Browse Source

自考app

haogh 2 months ago
parent
commit
8339c83a16
100 changed files with 7559 additions and 0 deletions
  1. BIN
      ses-app/app打包.mp4
  2. BIN
      ses-app/ses-app-android/app/app.jks
  3. 149 0
      ses-app/ses-app-android/app/build.gradle
  4. BIN
      ses-app/ses-app-android/app/libs/FaceImageLib-1.1.1.aar
  5. BIN
      ses-app/ses-app-android/app/libs/UPPayAssistEx.jar
  6. BIN
      ses-app/ses-app-android/app/libs/UPPayPluginExPro.jar
  7. BIN
      ses-app/ses-app-android/app/libs/android-gif-drawable-release@1.2.23.aar
  8. BIN
      ses-app/ses-app-android/app/libs/breakpad-build-release.aar
  9. BIN
      ses-app/ses-app-android/app/libs/bugly_crash_release.jar
  10. BIN
      ses-app/ses-app-android/app/libs/cameraviewplus-release(20221201AndroidX).aar
  11. BIN
      ses-app/ses-app-android/app/libs/fingerprint-release.aar
  12. BIN
      ses-app/ses-app-android/app/libs/jpeg-turbo-library-release.aar
  13. BIN
      ses-app/ses-app-android/app/libs/lib.5plus.base-release.aar
  14. BIN
      ses-app/ses-app-android/app/libs/liveness-interactive-online-shield-release.aar
  15. BIN
      ses-app/ses-app-android/app/libs/oaid_sdk_1.0.25.aar
  16. BIN
      ses-app/ses-app-android/app/libs/qmf-ppplugin-android-3.1.3.aar
  17. BIN
      ses-app/ses-app-android/app/libs/uniapp-v8-release.aar
  18. 31 0
      ses-app/ses-app-android/app/proguard-rules.pro
  19. 146 0
      ses-app/ses-app-android/app/src/main/AndroidManifest.xml
  20. BIN
      ses-app/ses-app-android/app/src/main/assets/M_Align_occlusion.model
  21. BIN
      ses-app/ses-app-android/app/src/main/assets/M_Detect_Hunter_SmallFace.model
  22. BIN
      ses-app/ses-app-android/app/src/main/assets/M_Face_Quality_Assessment.model
  23. BIN
      ses-app/ses-app-android/app/src/main/assets/M_Liveness_Cnn_half.model
  24. 79 0
      ses-app/ses-app-android/app/src/main/assets/SenseID_Liveness_Interactive.lic
  25. BIN
      ses-app/ses-app-android/app/src/main/assets/data.bin
  26. 5 0
      ses-app/ses-app-android/app/src/main/assets/data/dcloud_control.xml
  27. 92 0
      ses-app/ses-app-android/app/src/main/assets/data/dcloud_error.html
  28. 48 0
      ses-app/ses-app-android/app/src/main/assets/data/dcloud_properties.xml
  29. 28 0
      ses-app/ses-app-android/app/src/main/assets/dcloud_uniplugins.json
  30. 1 0
      ses-app/ses-app-android/app/src/main/assets/license.dat
  31. 7 0
      ses-app/ses-app-android/app/src/main/assets/option.cfg
  32. BIN
      ses-app/ses-app-android/app/src/main/assets/trData.mdl
  33. 91 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/AlipayMiniProgramCallbackActivity.java
  34. 319 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/CustomOcrDecode.java
  35. 96 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/ImageUitl.java
  36. 30 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/IntegerUtil.java
  37. 111 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/LivenessModule.java
  38. 49 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/MyApplication.java
  39. 257 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/OcrDocModule.java
  40. 55 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/PayModule.java
  41. 226 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/PreferenceUtil.java
  42. 437 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/SimpleCustomUIDocActivity.java
  43. 113 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/YJFaceActivity.java
  44. 75 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/YJFaceModule.java
  45. 62 0
      ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/wxapi/WXEntryActivity.java
  46. 150 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/AutoFocusManager.java
  47. 21 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/BaseApplication.java
  48. 43 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/CameraUtil.java
  49. 161 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/ImageUtil.java
  50. 36 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/MyFileManager.java
  51. 42 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/NetUtil.java
  52. 25 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/OnClickListener.java
  53. 655 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/OumasoftActivity.java
  54. 98 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/SharedPreferencesUtils.java
  55. 862 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/TakePhoto.java
  56. 29 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/ToastUtil.java
  57. 105 0
      ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/ViewUtil.java
  58. 365 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/AbstractCommonMotionLivingActivity.java
  59. 94 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ActivityUtils.java
  60. 79 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ImageManager.java
  61. 386 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/MotionLivenessActivity.java
  62. 210 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/fragment/MotionStepControlFragment.java
  63. 40 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/type/StepBean.java
  64. 409 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ui/camera/SenseCamera.java
  65. 323 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ui/camera/SenseCameraPreview.java
  66. 45 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/DensityUtil.java
  67. 86 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/MediaController.java
  68. 32 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/NetworkUtil.java
  69. 31 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/ToastUtil.java
  70. 117 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/AbstractOverlayView.java
  71. 145 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/CircleTimeView.java
  72. 34 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/FixedSpeedScroller.java
  73. 17 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/ITimeViewBase.java
  74. 44 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/InteractiveLivenessOverlayView.java
  75. 103 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/TimeViewContoller.java
  76. 244 0
      ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/WaterRippleView.java
  77. 11 0
      ses-app/ses-app-android/app/src/main/res/anim/anim_rotate.xml
  78. 5 0
      ses-app/ses-app-android/app/src/main/res/drawable-anydpi-v21/base_ic_baseline_photo_album_56_nor.xml
  79. 5 0
      ses-app/ses-app-android/app/src/main/res/drawable-anydpi-v21/base_ic_baseline_photo_album_56_press.xml
  80. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-hdpi-v4/base_ic_baseline_photo_album_56_nor.png
  81. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-hdpi-v4/base_ic_baseline_photo_album_56_press.png
  82. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_loading.png
  83. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_motion_step_done.png
  84. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_motion_step_ing.png
  85. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_motion_step_wait.png
  86. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-ldpi-v4/base_ic_baseline_photo_album_56_nor.png
  87. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-ldpi-v4/base_ic_baseline_photo_album_56_press.png
  88. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-mdpi-v4/base_ic_baseline_photo_album_56_nor.png
  89. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-mdpi-v4/base_ic_baseline_photo_album_56_press.png
  90. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xhdpi-v4/base_ic_baseline_photo_album_56_nor.png
  91. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xhdpi-v4/base_ic_baseline_photo_album_56_press.png
  92. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_loading.png
  93. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_motion_step_done.png
  94. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_motion_step_ing.png
  95. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_motion_step_wait.png
  96. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi-v4/base_ic_baseline_photo_album_56_nor.png
  97. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi-v4/base_ic_baseline_photo_album_56_press.png
  98. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi/common_ic_loading.png
  99. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi/common_ic_motion_step_done.png
  100. BIN
      ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi/common_ic_motion_step_ing.png

BIN
ses-app/app打包.mp4


BIN
ses-app/ses-app-android/app/app.jks


+ 149 - 0
ses-app/ses-app-android/app/build.gradle

@@ -0,0 +1,149 @@
+apply plugin: 'com.android.application'
+
+def getVersionCode() {// 获取版本号
+    def versionFile = file('version.properties')// 读取第一步新建的文件
+    if (versionFile.canRead()) {// 判断文件读取异常
+        Properties versionProps = new Properties()
+        versionProps.load(new FileInputStream(versionFile))
+        def versionCode = versionProps['VERSION_CODE'].toInteger()// 读取文件里面的版本号
+        def runTasks = gradle.startParameter.taskNames
+//        if ('assembleRelease' in runTasks) {//仅在assembleRelease任务是增加版本号,其他渠道包在此分别配置
+        if ('assemble' in runTasks) {
+            // 版本号自增之后再写入文件(此处是关键,版本号自增+1)
+            versionProps['VERSION_CODE'] = (++versionCode).toString()
+            versionProps.store(versionFile.newWriter(), null)
+        }
+        return versionCode // 返回自增之后的版本号
+    } else {
+        throw new GradleException("Could not find version.properties!")
+    }
+}
+
+def getVersionName() {// 获取版本号
+    def versionFile = file('version.properties')// 读取第一步新建的文件
+    if (versionFile.canRead()) {// 判断文件读取异常
+        Properties versionProps = new Properties()
+        versionProps.load(new FileInputStream(versionFile))
+        def versionName = versionProps['VERSION_NAME'].toString()// 读取文件里面的版本号
+        return versionName // 返回自增之后的版本号
+    } else {
+        throw new GradleException("Could not find version.properties!")
+    }
+}
+
+android {
+    compileSdkVersion 30
+    def defaultVersionName = getVersionName()
+    def defaultVersionCode = getVersionCode()
+    defaultConfig {
+        applicationId "cn.gxeea.zk"
+        minSdkVersion 24
+        //noinspection ExpiredTargetSdkVersion
+        targetSdkVersion 30
+        versionCode defaultVersionCode
+        versionName defaultVersionName
+        multiDexEnabled true
+
+        compileOptions {
+            sourceCompatibility JavaVersion.VERSION_1_8
+            targetCompatibility JavaVersion.VERSION_1_8
+        }
+
+        sourceSets {
+            main {
+                jniLibs.srcDirs = ['libs', "src/main/jniLibs"]
+            }
+        }
+        ndk {
+          //  abiFilters "armeabi"
+            abiFilters 'armeabi-v7a','arm64-v8a'
+        }
+    }
+    signingConfigs {
+        config {
+            keyAlias 'ses'
+            keyPassword '123456'
+            storeFile file('./app.jks')
+            storePassword '123456'
+            v1SigningEnabled true
+            v2SigningEnabled true
+        }
+    }
+
+    buildTypes {
+        debug {
+            signingConfig signingConfigs.config
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+        release {
+            signingConfig signingConfigs.config
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    packagingOptions {
+        jniLibs {
+            keepDebugSymbols += ['*/arm64-v8a/*.so', '*/armeabi-v7a/*.so']
+            pickFirsts += ['lib/*/libc++_shared.so']
+            useLegacyPackaging true
+        }
+    }
+
+    androidResources {
+        ignoreAssetsPattern '!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~'
+        additionalParameters '--auto-add-overlay'
+    }
+    namespace 'cn.gxeea.zk'
+    android.applicationVariants.configureEach { variant ->
+        variant.outputs.all {
+            outputFileName = "ses-app-" + variant.name + "-" + variant.versionName + "-build(" + variant.versionCode + ")" + getTime() + ".apk"
+        }
+    }
+}
+
+static def getTime() {
+    return "-" + new Date().format("yyyyMMddHHmm", TimeZone.getTimeZone("UTC"))
+}
+//
+dependencies {
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    implementation fileTree(include: ['*.aar'], dir: 'libs')
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation 'androidx.multidex:multidex:2.0.1'
+//
+//    implementation 'androidx.appcompat:appcompat:1.0.0'
+    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+//    implementation 'androidx.recyclerview:recyclerview:1.0.0'
+    implementation 'com.facebook.fresco:fresco:2.5.0'
+    implementation "com.facebook.fresco:animated-gif:2.5.0"
+    implementation 'com.github.bumptech.glide:glide:4.16.0'
+//    implementation 'com.github.bumptech.glide:glide:4.9.0'
+    implementation 'com.alibaba:fastjson:1.1.46.android'
+    implementation 'com.google.code.gson:gson:2.8.6'
+
+    implementation 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:5.5.8'
+//    implementation 'androidx.annotation:annotation-jvm:1.7.0'
+//    implementation fileTree(dir: 'libs', include: ['*.aar'])
+
+    implementation 'androidx.appcompat:appcompat:1.3.1'
+    implementation 'com.google.android.material:material:1.4.0'
+    implementation 'androidx.recyclerview:recyclerview:1.1.0'
+    implementation 'androidx.annotation:annotation:1.2.0'
+//    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+//    testImplementation 'junit:junit:4.13.2'
+//    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+//    implementation fileTree(dir: '../app/libs', include: ['FaceImageLib-1.1.0.aar'])
+
+    implementation 'com.squareup.okhttp3:okhttp:4.11.0'
+//    implementation "com.android.support:support-v4:28.0.0"
+//    implementation "com.android.support:appcompat-v7:28.0.0"
+//    implementation 'com.android.support:recyclerview-v7:28.0.0'
+//    implementation 'com.facebook.fresco:fresco:1.13.0'
+//    implementation "com.facebook.fresco:animated-gif:1.13.0"
+//    implementation 'com.github.bumptech.glide:glide:4.9.0'
+//    implementation 'com.alibaba:fastjson:1.1.46.android'
+}
+

BIN
ses-app/ses-app-android/app/libs/FaceImageLib-1.1.1.aar


BIN
ses-app/ses-app-android/app/libs/UPPayAssistEx.jar


BIN
ses-app/ses-app-android/app/libs/UPPayPluginExPro.jar


BIN
ses-app/ses-app-android/app/libs/android-gif-drawable-release@1.2.23.aar


BIN
ses-app/ses-app-android/app/libs/breakpad-build-release.aar


BIN
ses-app/ses-app-android/app/libs/bugly_crash_release.jar


BIN
ses-app/ses-app-android/app/libs/cameraviewplus-release(20221201AndroidX).aar


BIN
ses-app/ses-app-android/app/libs/fingerprint-release.aar


BIN
ses-app/ses-app-android/app/libs/jpeg-turbo-library-release.aar


BIN
ses-app/ses-app-android/app/libs/lib.5plus.base-release.aar


BIN
ses-app/ses-app-android/app/libs/liveness-interactive-online-shield-release.aar


BIN
ses-app/ses-app-android/app/libs/oaid_sdk_1.0.25.aar


BIN
ses-app/ses-app-android/app/libs/qmf-ppplugin-android-3.1.3.aar


BIN
ses-app/ses-app-android/app/libs/uniapp-v8-release.aar


+ 31 - 0
ses-app/ses-app-android/app/proguard-rules.pro

@@ -0,0 +1,31 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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
+-optimizationpasses 5
+ #// 混淆优化次数
+-dontusemixedcaseclassnames
+ #// 不使用混合命名方式
+-dontskipnonpubliclibraryclasses
+#// 不略过非公共库类
+-verbose
+#// 输出详细信息
+-dontwarn com.tencent.bugly.**
+-keep public class com.tencent.bugly.**{*;}

+ 146 - 0
ses-app/ses-app-android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+>
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-feature android:name="android.hardware.camera" android:required="true"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
+    <uses-permission
+            android:name="android.permission.READ_LOGS"
+            tools:ignore="ProtectedPermissions"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.NFC"/>
+
+    <uses-feature android:name="android.hardware.nfc.hce"/>
+    <uses-permission android:name="org.simalliance.openmobileapi.SMARTCARD"/>
+
+    <meta-data
+            android:name="PPPaySDK"
+            android:value="true"/>
+    <meta-data
+            android:name="CurrentEnvironment"
+            android:value="PROD"/>
+
+    <application
+            android:usesCleartextTraffic="false"
+            android:name=".MyApplication"
+            tools:replace="android:name,android:usesCleartextTraffic"
+            android:allowBackup="true"
+            android:allowClearUserData="true"
+            android:icon="@mipmap/ic_launcher"
+            android:requestLegacyExternalStorage="true"
+            android:label="@string/app_name"
+            android:largeHeap="true"
+            android:screenOrientation="portrait"
+            android:supportsRtl="true" tools:targetApi="m">
+        <activity
+                android:name=".wxapi.WXEntryActivity"
+                android:exported="true"
+                android:taskAffinity="com.chinaums.onlineservice"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar"
+                android:launchMode="singleTop"/>
+        <activity
+                android:name=".AlipayMiniProgramCallbackActivity"
+                android:launchMode="singleTask"
+                android:theme="@android:style/Theme.Translucent.NoTitleBar"
+                android:taskAffinity="com.chinaums.onlineservice">
+            <intent-filter>
+                <!--action.VIEW和category.DEFAULT必须设置-->
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <!--如果需要浏览器支持打开,则category.BROWSABLE-->
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <!--schema的协议类型:可以自行设置,只要按照统一规则,前后端一致就行-->
+                <data android:scheme="cngxeeazk"/>
+            </intent-filter>
+        </activity>
+        <activity
+                android:name="io.dcloud.PandoraEntry"
+                android:configChanges="orientation|keyboardHidden|keyboard|navigation"
+                android:label="@string/app_name"
+                android:launchMode="singleTask"
+                android:hardwareAccelerated="true"
+                android:theme="@style/DCloudTheme"
+                android:screenOrientation="portrait"
+                android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity
+                android:name="io.dcloud.PandoraEntryActivity"
+                android:launchMode="singleTask"
+                android:configChanges="orientation|keyboardHidden|screenSize|mcc|mnc|fontScale|keyboard|smallestScreenSize|screenLayout|screenSize"
+                android:hardwareAccelerated="true"
+                android:permission="com.miui.securitycenter.permission.AppPermissionsEditor"
+                android:screenOrientation="portrait"
+                android:theme="@style/DCloudActivityTheme"
+                android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+
+                <action android:name="android.intent.action.VIEW"/>
+                <data android:scheme="h56131bcf"/>
+            </intent-filter>
+        </activity>
+        <activity
+                android:name="com.turui.android.activity.WCameraActivity"
+                android:screenOrientation="landscape"
+                android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+                tools:replace="android:theme" tools:ignore="Instantiatable">
+        </activity>
+        <activity
+                android:name=".SimpleCustomUIDocActivity"
+                android:screenOrientation="landscape"
+                android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+                tools:replace="android:theme">
+        </activity>
+        <activity
+                android:name="com.turui.android.activity.CameraActivity"
+                android:screenOrientation="landscape"
+                android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+                tools:replace="android:theme">
+        </activity>
+<!--        <activity-->
+<!--                android:name=".YJFaceActivity"-->
+<!--                android:theme="@style/Theme.AppCompat.Light.NoActionBar">-->
+<!--        </activity>-->
+<!--        <activity-->
+<!--                android:name="com.yunjisoft.ksout.ui.CollectPhotoActivity"-->
+<!--                android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>-->
+        <activity
+                android:name="com.turui.liveness.motion.MotionLivenessActivity"
+                android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+                android:screenOrientation="portrait"/>
+        <activity
+                android:name="com.oumasoft.facelibrary.OumasoftActivity"
+                android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+        </activity>
+        <activity android:name="com.oumasoft.facelibrary.TakePhoto"
+                  android:theme="@style/Theme.FaceLibrary"/>
+        <provider
+                android:name="io.dcloud.common.util.DCloud_FileProvider"
+                android:authorities="cn.gxeea.zk.dc.fileprovider"
+                android:exported="false"
+                android:grantUriPermissions="true">
+            <meta-data
+                    android:name="android.support.FILE_PROVIDER_PATHS"
+                    android:resource="@xml/dcloud_file_provider"/>
+        </provider>
+        <meta-data
+                android:name="dcloud_appkey"
+                android:value="59c40dff098dd8e52ece0e6cd993d369"/>
+    </application>
+
+</manifest>

BIN
ses-app/ses-app-android/app/src/main/assets/M_Align_occlusion.model


BIN
ses-app/ses-app-android/app/src/main/assets/M_Detect_Hunter_SmallFace.model


BIN
ses-app/ses-app-android/app/src/main/assets/M_Face_Quality_Assessment.model


BIN
ses-app/ses-app-android/app/src/main/assets/M_Liveness_Cnn_half.model


+ 79 - 0
ses-app/ses-app-android/app/src/main/assets/SenseID_Liveness_Interactive.lic

@@ -0,0 +1,79 @@
+############################################################
+# SenseTime License
+# License Product: senseid_liveness
+# Expiration: 19400101~21990711
+# License SN: 155fffaa-6af1-492a-b6dd-27090a78954d
+############################################################
+sGfdd1A9IqXdI4EZzUCnnQ3bIjYX8gl6KXnNBybebzcX3SzguALIJW1Wdwjk
+YG7flU2kokfOQQXfBsQajKYm6a2jf9thpF0M06X5r4MG/nXRy+eaLmNyL23R
+D8In6LIF6QpBGkoQWi9bo4mQN5oATznLW7DdIByo8XzyxbOeghkOCz6zAQAA
+AAEAAACfYz9fI0UT6OtSfEBxcfbAXembtZ6Kb1YTJIKiQjjrz2TeSsln1sWx
+0t7iPx9HVNDm0dGYqtDNiCYPc+ArQALVvcH7QGR9MnXkhJScFP6ICLPVj0P2
+p1MdnG9EXZGfCsemnjiyo497jf2QlEpDYNm+ID+tZamPygJt5fY5zJbFCaxR
+PTf3+ZftRuxMyEZE352/axDcapwYOori4b6R4+OHSAM9XOcVN2t8xEhbaJk1
+1erGvjTyEeU5h3BwSKdVKKrdHKOumQUONdy9EbkEn7MP69jEPc+lJTxrXbDO
+zDZkLiDppHmucnGvT1hudr6k/X3CgxjJv4o92DeC4ilai3yTAQABAAAAAAAD
+AAAAAAAAAAAAAACTsfIr5/LBv+vFPQ8K8bm3LnDS4DDeYYGMEX5bhDHH/meW
+ef+j9jCmLUY7vEVhFxckVudWAxJgSC11T921RhVLn7/PNJgtOXFPt+Fy/wEs
+nZwziIjiFAdHUspopNygPZeZSfF6XTpLkmkVeM79b24qOZxTWsoG2l/xIAnz
++DiyQj3c1NZ25FG3uD+4mgpFdoimXvWL5FIaT16e7fhp5aRJXr1/+bbsVWdL
+A3eCmjHBJwb1DbzCh+oCqVdgquPbs7yJEJdB1F3xzMu0LSeFD2iIprEGsg86
+roxgUO8j7IOw5HPgo70wJWt6Oagazz8xs9qsSPHy9T8HN3++TW24ihFDIuVN
+q9ABOR3T1Rtd3O4Ax7ZaDA2EgrVg5n33Vv6pYXXF8weF3oPt2sRbBC4OIYMY
+7TcPewwSLQ==
+------------------------------------------------------------
+sGfdd6QA2kuf5oD7qHjiLWO4NfmvsZSoD9B3KiMkvXFc6P49diWRhOAAn4eg
+mO4LxBYneuGKNqlTLpduxXgLmciJ7zExGMROquqGcMsA36Z84RGSMUWRnfnG
+LYr/VxpSJiXN3vDWwaN9Nx7QsaC3pJNVm8xD3YgPfLUqg2xId6lmdOQZAQAA
+AAIAAABEqByu4gmhLKn/LWDEoeFcgl63qv4Gr0JKHLEfuB6fhVmDXJMP6wqx
+CZd3fko/PEeqe2ZSlDf7etxbNVwLNa2wdiCLs8sRGpruC40dv3KSvj9fn67o
+HxSUJwdW2ZC7BHaNCd2sBVBm23eATOQ+0Mcjcp7FitHZaPbYSeISC/nuDbat
+imqC7RK6aNql0UiFu5iJtg5859NQ1k5C4DS32BliaCESEe+R3oSZqIS/ld03
+ayY7VII/pD5amOc0Z3k1HGFumYBIfRN+4R7pXb7R/JJfgMyANtV/dbByW0/q
+0KtIf1hp19K+h/TSnoF75cLRrp3hBpJL+BSilKWnt7oBrKg5AAAAAAAAAAAA
+AAAAAAAAAAAAAAB7rOTBhb/gAz7/gBqmVeeyeov8MnfkkxX47qp3/zvoNx7B
+li/omy/OnAGfOJJp2ul8/FBT4aNqduFgzB3fUPBOYyqyfj6jxVKOHj5eOo/y
+fMnyR6cmlGMMoYK/tLKyFs7U2xCMaLXe70DuU266HGcpf0T/C/nIh27a2rr0
+qzqy/0khGVumlWOy9Lf1H37wNXYC7dCrlJ5VKUEZmcILsAdPbElZjVXjNosg
+t29kQaoDg0QvBzDGW83ETKpdi8Tg43Xn0RO562WkaZaAgPCZp6wy2SWJGPmc
+/txhmA7GmcM2cJehNGz/Bxa8BXV5UWn0j2vTD2j/b7uulrsouEhW148fTeOv
+dyZV0ghwIpGi4DkI/+zZkOJf04bGO9E+ILmEWcD/WFVrXkrAXQPVs6gWopV0
+HHIzjBIIHSxA3509NIhfsVXYqidDvfAWP3dnh1ClzRbVnQEdabHQiqYNlcge
+8kE+FuB9mEvi0kDwJhgSIog2hWXF17FnyH3hvZW8O+T/189mz5UzBF/2+DUz
+JdUR7oNih2r97U2Vmm0Z11CCVoziHCJUYUXpia3CWmEuSOViPcmQA7mTkndD
+BEHrLHXU4Mz1+fcRi2QbqArq+qBxtBzqka1BVcnQ4seAvnO515Qply/r0sRO
+1rXNLVQOwXuCderQYVcqdoXKWV1zQoiPABIP1gsZSp8kODyQ/GWYfs3Ae7M4
+/fuy6MvQgT2qMIwSVSREm9Vd5tcd7C0sU02zH2+QnNHeeI134SR7adP5ybhJ
+Qs8fdifmtEiq/xcc+PU8NFPtSvtERAaWedVMLWTcK+bwH+Fd9OlBdA42XoB1
+sm7nEe+9DUOfUirlrDWV3fjbGoSYrglsrAIOyS7TlnoVzyoEezsE8uueGkC7
+GNpPlw4soZQQ1ombUdLzcUNema9IWBhvn2g489h6En6j3CVRKbdd8Ph/EzAy
+B7WYZIZKGVp9WHwlrECet6V4Sq4bsyDU3MXLbjN69KZqiizMUsjPG0VobsCc
+YWqZwFcY8lK/AD2UYq/0ZO0CvcEnEdT6o4N4Jx8BYDq6KzZ8xgh3UCjSbju/
+q2+z+KbOzHPLkXH8Aaezo4dLcm+C5EXsUn8fSQP7fVO7fdpJqHMDJ6zJPgcE
+VtG4Q5SZCaEQ/g2DIO9LI/1LhC1Nv1EXPWXVn5deeSlqwRNvs2aEzycMx8G0
+F8OW8BnhuhFzhq6NQt/mNb+2rtwbzmdCzvA+DRIjhVpKYrxh5TPuW9/bTVp5
+yvQedNgVCRmJ2bGD+jkoYjcUybnNaNO/ujc3sjl09NypVSXNciuYW9Dnd+4f
+WKavOl9VuPrXjsauYnhUboqUdyrWZQtZQDZp1fK4LYB5PGlQfNpvE0Hq18rS
+RX4XJWl1SEtcwB0GwF++swEc2v+rDKR+sVmYlIoM3x0Jljjy9erXE6A8Q16w
+KxPs+XPyRmiApUnYd2WbhrLvtmPNJsMwFUYo/i/Yu25fv+YHk+z6ILtdiYkU
+x6PdYrP3B2pO3U2kqdp6RMKkGSHzoEoQfCwEB/dui1/HwpMiQCSMoCQ19afb
+2TPoB/v4CNLVz4W+YlDI/g9RkeICQRIn6u1bjaO2nxEqDqxOe2U7hVfmIG+C
+2MNK6HwdtCl5GlQ2k6igcQuhNKUMuIIk7PDY95KJR740aEzQB7jWjUKo/WBs
+e9wjoNsbODtZjMYFkfveLib6JSCYZsL+zNBvHip6jwZIGUy3kMfiPzAE04Bg
+QNGEyDISgg4C19tCbSTImEBJXvU/HBoUEFYdfLO2oCRKOBCbtulK1d54RhlX
+5KHMRKfw/dGSfgwzBB2StH57tJXIFpY1w4hYeTOSdP2Ec0+xLOFLMp7h5xsL
+8cXNgSPC+wuPFOzN46dAOnvfWsjE1jfy9iUkyiAK5bs1csa4QjMUTxghcR7N
+8jiHpNIHAAlBdVdBuGcNBLMRpR9tVYq9Dl/9l1QkqKG9yZbbzK2wMb2krm/u
+Z/8WVAMCvHa7vH1s0g+uPY2ceKYECT4h26qP8W1I9v8Tmcoo+O52ypX7XEQ6
+0v5j8gp/p54A/RB8LNLBoEhG2L3vq4vfO1TZbprQLJL7TbLBF/EsMAuoUPN+
+OolsNycdq9gaiZczp+LSIFlO+1DPyq6Z6V7zqKfDTKhe/wtsg3mVyT3Vsrlt
+uZ+zHgHTryJ8FJYQSknmEnGY1p3CfUeIn/z2AoibJ2o/n26ZGPojo5cNYH2l
++szBjqOtp0LmtgpYnHq0SlDCdCHn9YhRMlJcd8uyfAKuJAwWdekrIDaihfqW
+JoHDUanSIttRnQujhBLUFkfMZHkxDucFH9r2hd0hSh76+7Hl+u1PVduE9GAX
+N0UzR5gfVF7mZ7IKk7QW+siAp70ouPS+fG50LY4sI8GpGN/kI0Uku8QBugNh
+ywE4WePgZBfEjl2H8Hlzm4cqsRAUZwkJzsYtC6zuosS68J/3cPu+a4hIzIjb
+Fp0tTvHX0JPvgLATi9v76z3ajix+lj9f96noZoXQfl7b2S6KRL79nMBQZ+D3
+QmFkv++IPmvzFJgiDLU8gK1PesEWhipbqsjxxX1EnooFafGy3qCfAJfCjpGB
+FbPK94zNtmeHSPOPQDIaDvb1cP+zt3GlUZJmV15lVhDGrFDNWz6uMd+zi5KD
+SXYXBBVyP90c6hn5EpGgctIddvs6FNC0m9CpENH6+xO3f/32gCRdLZM=
+############################################################

BIN
ses-app/ses-app-android/app/src/main/assets/data.bin


+ 5 - 0
ses-app/ses-app-android/app/src/main/assets/data/dcloud_control.xml

@@ -0,0 +1,5 @@
+<hbuilder>
+<apps syncDebug="true" debug="true">
+    <app appid="__UNI__5CC300E" appver="1.0.0"/>
+</apps>
+</hbuilder>

+ 92 - 0
ses-app/ses-app-android/app/src/main/assets/data/dcloud_error.html

@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
+	<meta name="HandheldFriendly" content="true"/>
+	<meta name="MobileOptimized" content="320"/>
+	<title>Error</title>
+	<script type="text/javascript">
+// H5 plus事件处理
+var ws=null;
+function plusReady(){
+	// Android处理返回键
+	plus.key.addEventListener('backbutton',function(){
+		(history.length==1)&&ws.close();
+		var c=setTimeout(function(){
+			ws.close();
+		},1000);
+		window.onbeforeunload=function(){
+			clearTimeout(c);
+		}
+		history.go(-2);
+	},false);
+	ws=plus.webview.currentWebview();
+}
+if(window.plus){
+	plusReady();
+}else{
+	document.addEventListener('plusready',plusReady,false);
+}
+document.addEventListener('touchstart',function(){
+    return false;
+},true);
+// 禁止选择
+document.oncontextmenu=function(){
+	return false;
+};
+// 获取错误信息
+document.addEventListener("error",function(e){
+	info.innerText="请求的页面("+e.url+")无法打开";
+	console.log("请求的页面无法打开:"+e.href);
+},false);
+	</script>
+	<style>
+*{
+	-webkit-user-select: none;
+}
+html,body{
+	margin: 0px;
+	padding: 0px;
+	width: 100%;
+	height: 100%;
+	text-align: center;
+	word-break: break-all;
+	-webkit-touch-callout:none;
+	-webkit-tap-highlight-color:rgba(0,0,0,0);
+}
+.button{
+	width: 50%;
+	font-size: 18px;
+	font-weight: normal;
+	text-decoration: none;
+	text-align: center;
+	padding: .5em 0em;
+	margin: .5em auto;
+	color: #333333;
+	background-color: #EEEEEE;
+	border: 1px solid #CCCCCC;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+}
+.button:active{
+	background-color: #CCCCCC;
+}
+	</style>
+</head>
+<body>
+	<div style="width:100%;height:20%;"></div>
+	<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512" style="height:20%;width:30%"> 
+	<g id="icomoon-ignore">
+		<line stroke-width="1" x1="" y1="" x2="" y2="" stroke="#449FDB" opacity=""></line>
+	</g>
+	<path d="M256 0c-141.385 0-256 114.615-256 256s114.615 256 256 256 256-114.615 256-256-114.615-256-256-256zM352 128c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32zM160 128c17.673 0 32 14.327 32 32s-14.327 32-32 32-32-14.327-32-32 14.327-32 32-32zM352.049 390.37c-19.587-32.574-55.272-54.37-96.049-54.37s-76.462 21.796-96.049 54.37l-41.164-24.698c27.98-46.535 78.958-77.672 137.213-77.672s109.232 31.137 137.213 77.672l-41.164 24.698z" fill="#666666"></path>
+    </svg>
+	<p style="font-size:18px;font-weight:bolder;">We're sorry ...</p>
+	<p id="info" style="font-size:12px;"></p>
+	<!--<div class="button" onclick="history.back()">Retry</div>-->
+	<div class="button" onclick="if(history.length == 1){ws.close();}else{ws.back();ws.back();}">Back</div>
+	<div class="button" onclick="ws.close()">Close</div>
+	<div class="button" onclick="plus.runtime.restart()">Restart</div>
+</body>
+</html>

+ 48 - 0
ses-app/ses-app-android/app/src/main/assets/data/dcloud_properties.xml

@@ -0,0 +1,48 @@
+<properties>
+	<features>
+		<feature name="Barcode" value="io.dcloud.feature.barcode2.BarcodeFeatureImpl"/>
+		<feature name="Maps" value="io.dcloud.js.map.amap.JsMapPluginImpl"/>
+        <!--<feature name="Maps" value="io.dcloud.js.map.JsMapPluginImpl"/>-->
+		<feature name="Contacts" value="io.dcloud.feature.contacts.ContactsFeatureImpl"/>
+		<feature name="Messaging" value="io.dcloud.adapter.messaging.MessagingPluginImpl"/>
+		<feature name="Camera" value="io.dcloud.js.camera.CameraFeatureImpl"/>
+		<feature name="Console" value="io.dcloud.feature.pdr.LoggerFeatureImpl"/>
+		<feature name="Device" value="io.dcloud.feature.device.DeviceFeatureImpl"/>
+		<feature name="File" value="io.dcloud.js.file.FileFeatureImpl"/>
+		<feature name="Proximity" value="io.dcloud.feature.sensor.ProximityFeatureImpl"/>
+		<feature name="Storage" value="io.dcloud.feature.pdr.NStorageFeatureImpl"/>
+		<feature name="Cache" value="io.dcloud.feature.pdr.CoreCacheFeatureImpl"/>
+		<feature name="Invocation" value="io.dcloud.invocation.Invocation"/>
+		<feature name="Navigator" value="io.dcloud.feature.ui.navigator.NavigatorUIFeatureImpl"/>
+		<feature name="NativeUI" value="io.dcloud.feature.ui.nativeui.NativeUIFeatureImpl"/>
+		<feature name="UI" value="io.dcloud.feature.ui.UIFeatureImpl">
+			<module name="Navigator" value="io.dcloud.feature.ui.NavView"/>
+		</feature>
+		<feature name="Gallery" value="io.dcloud.js.gallery.GalleryFeatureImpl"/>
+		<feature name="Downloader" value="io.dcloud.net.DownloaderFeatureImpl"/>
+		<feature name="Uploader" value="io.dcloud.net.UploadFeature"/>
+		<feature name="Zip" value="io.dcloud.feature.pdr.ZipFeature"/>
+		<feature name="Audio" value="io.dcloud.feature.audio.AudioFeatureImpl"/>
+		<feature name="Runtime" value="io.dcloud.feature.pdr.RuntimeFeatureImpl"/>
+        <feature name="VideoPlayer" value="io.dcloud.media.MediaFeatureImpl"/>
+        <feature name="LivePusher" value="io.dcloud.media.live.LiveMediaFeatureImpl"/>
+		<feature name="XMLHttpRequest" value="io.dcloud.net.XMLHttpRequestFeature"/>
+		<feature name="Statistic" value="io.dcloud.feature.statistics.StatisticsFeatureImpl"/>
+		<feature name="Accelerometer" value="io.dcloud.feature.sensor.AccelerometerFeatureImpl"/>
+		<feature name="Orientation" value="io.dcloud.feature.sensor.OrientationFeatureImpl"/>
+		<feature name="NativeObj" value="io.dcloud.feature.nativeObj.FeatureImpl"/>		
+		<feature name="Geolocation" value="io.dcloud.js.geolocation.GeolocationFeatureImpl"/>
+		<feature name="Stream" value="io.dcloud.appstream.js.StreamAppFeatureImpl"/>
+        <feature name="plugintest" value="com.example.H5PlusPlugin.PGPlugintest"/>
+		<feature name="Fingerprint" value="io.dcloud.feature.fingerprint.FingerPrintsImpl"/>
+<!--		<feature name="Payment" value="io.dcloud.feature.payment.PaymentFeatureImpl"><module name="AliPay" value="io.dcloud.feature.payment.alipay.AliPay"/></feature>-->
+<!--		<feature name="Payment" value="io.dcloud.feature.payment.PaymentFeatureImpl"><module name="Payment-Weixin" value="io.dcloud.feature.payment.weixin.WeiXinPay"/></feature>-->
+	</features>
+
+	<services>
+		<service name="push" value="io.dcloud.feature.aps.APSFeatureImpl"/>
+		<service name="Statistic" value="io.dcloud.feature.statistics.StatisticsBootImpl"/>
+		<service name="Downloader" value="io.dcloud.net.DownloaderBootImpl"/>
+		<!--<service name="Maps" value="io.dcloud.js.map.MapInitImpl"/>-->
+	</services>
+</properties>

+ 28 - 0
ses-app/ses-app-android/app/src/main/assets/dcloud_uniplugins.json

@@ -0,0 +1,28 @@
+{
+  "nativePlugins": [
+    {
+      "plugins": [
+        {
+          "type": "module",
+          "name": "SesAppPlugin-OcrDocModule",
+          "class": "cn.gxeea.zk.OcrDocModule"
+        },
+        {
+          "type": "module",
+          "name": "SesAppPlugin-YJFaceModule",
+          "class": "cn.gxeea.zk.YJFaceModule"
+        },
+        {
+          "type": "module",
+          "name": "SesAppPlugin-LivenessModule",
+          "class": "cn.gxeea.zk.LivenessModule"
+        },
+        {
+          "type": "module",
+          "name": "SesAppPlugin-PayModule",
+          "class": "cn.gxeea.zk.PayModule"
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
ses-app/ses-app-android/app/src/main/assets/license.dat

@@ -0,0 +1 @@
+rbGafRvb0wJQVpDotHrx/YjpnU8Ij16UOgVHIL2MG6ytCR7SCCkBhUKW796bTWPqIWvYke1nu5lAf4Mr6EvZeW3KXaTdlgGjwwiB9DTEPvu9wZeH8skuFyg0P2/SHCRy8cbomo5K4/ZG6npClrAURp69BxF4/8mrUnsKMvOd/L0mfXCtnCFRcrA3vPyyQGefOZJuVhmTqdl3M710VHIoh+OAUTN1ozvoGAMbXAuUw13WbR47VGKpgoAMLzL2TypbNBmY9wU9XQxHTwe5ugd9+BsPl4/NX1wIUrkl0txtxLB8Uj3SXR1zEWAX4VtRSVgSsyOVdB36BCyYcY9gHtXZ1d+mtHvsBcO1lJxUm3S24bzgTsDVWBqgnCLlQbvTSsTeVpF0a1W8It39/V0oHKdhWc/8aLERgqvgSoqNHRpGamIIQGhqYzMjh0TAA4wWXrERueRklL0VJwX6/S36rVViuv8Yz1pT26z3IGDzHRbC8Ww17Xlure6GU62flbI+E6M/Iqu8hw9DW5apXG5Z00r1wWalEy191R1EbKQ/xu03R854h+/iIZIpjUZCX6vrVDlYGOuoqPJOG7wRbdWh8YPZcS3NvNqr6E0HYuvuhwBpEllHBIdaxgkBKGo+6dTF/iSsQjF6v8Mx/gCZZgkINwGhH5VXe2dfuKM+JVJdY1egSkt7oQYOSdJkt4U2aniERREIWcoRzLvtyDpvVBYAHXHGZK9HfY6MM6OIikyOyPyYwpVAxJ8ReyylwA/sfumXGM5YN/+kFmAqoC7xG4uEFRp9fw==

+ 7 - 0
ses-app/ses-app-android/app/src/main/assets/option.cfg

@@ -0,0 +1,7 @@
+;/*授权文件路径*/
+[TR_CFG_LIC]
+LIC_NAME=license.dat
+
+;/*字库路径配置*/
+[TR_CFG_MDL]
+MDL_NAME=trData.mdl

BIN
ses-app/ses-app-android/app/src/main/assets/trData.mdl


+ 91 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/AlipayMiniProgramCallbackActivity.java

@@ -0,0 +1,91 @@
+package cn.gxeea.zk;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.appcompat.app.AlertDialog;
+import io.dcloud.PandoraEntryActivity;
+
+import java.util.HashMap;
+
+public class AlipayMiniProgramCallbackActivity extends Activity {
+    TextView tv;
+    HashMap<String,String> payResultCode = new HashMap<>();
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        startActivity(new Intent(this, PandoraEntryActivity.class));
+    }
+
+    private  void initPayResultCode(){
+        payResultCode.put("0000","支付请求发送成功。商户订单是否成功支付应该以商户后台收到支付结果。");
+        payResultCode.put("1000","用户取消支付");
+        payResultCode.put("1001","参数错误");
+        payResultCode.put("1002","网络连接错误");
+        payResultCode.put("1003","支付客户端未安装");
+        payResultCode.put("2001","订单处理中,支付结果未知(有可能已经支付成功),请通过后台接口查询订单状态");
+        payResultCode.put("2002","订单号重复");
+        payResultCode.put("2003","订单支付失败");
+        payResultCode.put("9999","其他支付错误");
+    }
+    private String getResultMsg(String code){
+        if(!TextUtils.isEmpty(code)){
+            code = code.trim();
+        }
+        String msg = payResultCode.get(code);
+        if(TextUtils.isEmpty(msg)){
+            msg = "状态不存在";
+        }
+        return msg;
+    }
+    private void showMsg() {
+        if(getIntent() != null){
+            try {
+                Uri uri = getIntent().getData();
+                //完整路径
+                String url = uri.toString();
+
+                String errCode = uri.getQueryParameter("errCode");
+                String errStr = uri.getQueryParameter("errStr");
+
+                String str = "支付结果 ===》 errCode = " + errCode + " ------ errStr = " + errStr + "\n 支付状态 ---> " + getResultMsg(errCode);
+
+                tv.setText("Scheme url="+url+"\n ------------ \n" + str );
+            }catch (Exception e){
+                e.getStackTrace();
+                showMsgDialog(e.getMessage());
+            }
+
+        }else {
+            tv.setText("内容为空的");
+        }
+    }
+
+    private void showMsgDialog(final String  msg) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                AlertDialog dialog = new AlertDialog.Builder(AlipayMiniProgramCallbackActivity.this).setMessage(msg).
+                        setPositiveButton("确定", new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialogInterface, int i) {
+                                dialogInterface.dismiss();
+                            }
+                        }).create();
+                dialog.setCanceledOnTouchOutside(false);
+                dialog.show();
+            }
+        });
+    }
+    public void toast(String click) {
+        Toast.makeText(this,click,Toast.LENGTH_SHORT).show();
+
+    }
+
+
+}

+ 319 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/CustomOcrDecode.java

@@ -0,0 +1,319 @@
+package cn.gxeea.zk;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import com.idcard.*;
+import com.turui.android.activity.CameraActivity;
+import com.turui.engine.EngineConfig;
+import com.turui.engine.InfoCollection;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CustomOcrDecode {
+
+    private static final int TR_RECOCR_OK = 200;  // 流程正常
+    private static final int TR_ERR_HTTP_RECEIVE_400 = 400;  // 服务器返回的错误
+    private static final int TR_RECOCR_FAIL = 800;  // 流程失败
+    private static final int TR_ENGINT_PARAM_FAIL = 801;  // 引擎设置参数失败
+    private static final int TR_LOADIMAGE_FAIL = 802;  // 加载图片失败
+    private static final int TR_PICTURE_NOT_SUPPORT = 803;  // 图片格式不支持
+
+    private static final int PARAM_ENABLE = 1; // 开启该功能
+    private static final int PARAM_DISENABLE = 0; // 关闭该功能
+
+    // 注意这里是图片模式(区分图片与扫描)
+    private static final int BANK_PHOTO_MODEL = 1; // 银行卡图片模式
+    private static final int NO_BANK_PHOTO_MODEL = 0; // 非银行卡图片模式
+//    private static final int BANK_SCAN_MODEL = 0; // 银行卡扫描模式
+//    private static final int NO_BANK_SCAN_MODEL = 1; // 非银行卡扫描模式
+
+    private static final Map<Integer, String> MESSAGE_INFO = new HashMap<Integer, String>() {
+        {
+            put(TR_RECOCR_OK, "TR_RECOCR OK");
+            put(TR_ERR_HTTP_RECEIVE_400, "HTTP_RECEIVE Fail(信息检验失败)");
+            put(TR_RECOCR_FAIL, "TR_RECOCR Fail");
+            put(TR_ENGINT_PARAM_FAIL, "engineParam() Fail");
+            put(TR_LOADIMAGE_FAIL, "TR_LoadImage Fail");
+            put(TR_PICTURE_NOT_SUPPORT, "File type not support");
+        }
+    };
+
+//		TRECAPI engine = new TRECAPIImpl();//可以多次使用
+//		TStatus tStatus = engine.TR_StartUP(null, engine.TR_GetEngineTimeKey());//初始化,可以多次使用
+//		if (tStatus == TStatus.TR_TIME_OUT) {
+//			Log.i(TAG, "引擎过期");
+//			return null;
+//		}
+//      CustomOcrDecode.decodeByxxx
+//      //确定不再使用时释放整个引擎内存
+//		engine.TR_ClearUP();
+
+    /**
+     * 路径方式,不支持中文路径与中文文件名(不支持PNG)
+     * 如果有中文的请复制到英文路径下再导入 或 使用 decodeByBitmap() 先读取成 RGB565 格式再导入
+     *
+     * @param path
+     * @param engine
+     * @param tengineID
+     * @return
+     */
+    @Deprecated
+    public static InfoCollection decodeByPath(EngineConfig config, String path, TRECAPI engine, TengineID tengineID) {
+        // 网上的方法,判断是不是支持的格式
+//        String fileType = GetTypeByHead.getFileType(new File(path));
+//        Log.d("CustomOcrDecode", fileType);
+//        if ("png".equals(fileType)) {
+//            InfoCollection info = new InfoCollection();
+//            info.setCode(TR_PICTURE_NOT_SUPPORT);
+//            info.setDesc(MESSAGE_CODE.get(TR_PICTURE_NOT_SUPPORT));
+//            return info;
+//            建议这里使用 RGB565 方式识别
+//        }
+
+        InfoCollection info = new InfoCollection();
+        TStatus tStatus = engineParam(config, engine, tengineID, null);
+        if (tStatus != TStatus.TR_OK) {
+            info.setCode(TR_ENGINT_PARAM_FAIL);
+            info.setDesc(MESSAGE_INFO.get(TR_ENGINT_PARAM_FAIL));
+            return info;
+        }
+        // 加载图片到引擎
+        tStatus = engine.TR_LoadImage(path);
+        if (tStatus != TStatus.TR_OK) {
+            info.setCode(TR_LOADIMAGE_FAIL);
+            info.setDesc(MESSAGE_INFO.get(TR_LOADIMAGE_FAIL));
+            return info;
+        }
+        // 测试用
+//        engine.TR_SaveImage(Environment.getExternalStorageDirectory().getPath() + "/atrocr/img/" + System.currentTimeMillis() + "_path.jpg");
+
+        info = ocrDecode(config, engine, tengineID);
+        // 释放图片内存
+        engine.TR_FreeImage();// 有加载图片必须释放图片内存
+        return info;
+    }
+
+    /**
+     * RGB565格式 Bitmap 方式
+     *
+     * @param RGB565 必需使用 RGB565
+     * @param engine
+     * @param tengineID
+     * @return
+     */
+    public static InfoCollection decodeByBitmap(EngineConfig config, Bitmap RGB565, TRECAPI engine, TengineID tengineID) {
+        InfoCollection info = new InfoCollection();
+        TStatus tStatus = engineParam(config, engine, tengineID, RGB565);
+        if (tStatus != TStatus.TR_OK) {
+            info.setCode(TR_ENGINT_PARAM_FAIL);
+            info.setDesc(MESSAGE_INFO.get(TR_ENGINT_PARAM_FAIL));
+            return info;
+        }
+        // 加载图片到引擎
+        tStatus = engine.TR_LoadMemBitMap(RGB565);
+        if (tStatus != TStatus.TR_OK) {
+            info.setCode(TR_LOADIMAGE_FAIL);
+            info.setDesc(MESSAGE_INFO.get(TR_LOADIMAGE_FAIL));
+            return info;
+        }
+        // 测试用
+//        engine.TR_SaveImage(Environment.getExternalStorageDirectory().getPath() + "/atrocr/img/" + System.currentTimeMillis() + "_bitmap.jpg");
+
+        info = ocrDecode(config, engine, tengineID);
+        // 释放图片内存
+        engine.TR_FreeImage();// 有加载图片必须释放图片内存
+        return info;
+    }
+
+    /**
+     * 设置相关参数
+     * @param engine
+     * @param tengineID
+     * @return
+     */
+    private static TStatus engineParam(EngineConfig config, TRECAPI engine, TengineID tengineID, Bitmap RGB565) {
+        TStatus tStatus;
+        // 设置引擎参数
+        tStatus = engine.TR_SetSupportEngine(tengineID);// 打开引擎类型,测试是用身份证, 其他证件以此类推
+        if (tStatus != TStatus.TR_OK) {
+            return tStatus;
+        }
+        if (tengineID == TengineID.TIDBANK) {
+            //银行卡相反的
+            engine.TR_SetParam(TParam.T_SET_RECMODE, BANK_PHOTO_MODEL);
+        } else {
+            engine.TR_SetParam(TParam.T_SET_RECMODE, NO_BANK_PHOTO_MODEL);//身份证或其它证件拍照,非银行卡
+        }
+        engine.TR_SetParam(TParam.T_SET_HEADIMG, PARAM_ENABLE);// 打开身份证人头像功能,银行卡小图功能
+        engine.TR_SetParam(TParam.T_SET_BANK_LINE_STREAM, PARAM_ENABLE);//银行卡小图新版用流形式
+
+        if (tengineID == TengineID.TIDBQLABLE && null != RGB565) {
+            engine.TR_SetParam(TParam.T_SET_BQ_WIDTH, RGB565.getWidth());
+            engine.TR_SetParam(TParam.T_SET_BQ_HEIGHT, RGB565.getHeight());
+        }
+
+//        engine.TR_SetParam(TParam.T_SET_EVALUE_QUALITY, 1);
+
+        return tStatus;
+    }
+
+    /**
+     * 识别获取信息
+     * @param engine
+     * @param tengineID
+     * @return
+     */
+    private static InfoCollection ocrDecode(EngineConfig config, TRECAPI engine, TengineID tengineID) {
+        InfoCollection info = new InfoCollection();
+        // 引擎识别
+        TStatus tStatus = engine.TR_RECOCR();
+        if (tStatus == TStatus.ERR_HTTP_RECEIVE_400) { // 如果使用按次等服务才会有这个
+            info.setCode(TR_ERR_HTTP_RECEIVE_400);
+            info.setDesc(MESSAGE_INFO.get(TR_ERR_HTTP_RECEIVE_400));
+            return info;
+        }
+        if (tStatus != TStatus.TR_OK) {
+            info.setCode(TR_RECOCR_FAIL);
+            info.setDesc(MESSAGE_INFO.get(TR_RECOCR_FAIL));
+            return info;
+        }
+
+        // 获取识别结果
+        info.setCode(TR_RECOCR_OK);
+        info.setDesc(MESSAGE_INFO.get(TR_RECOCR_OK));
+        // 根据对应的字段获取单个栏目信息, 获取其他信息, 以此类推
+        // engine.TR_GetOCRFieldStringBuf(TFieldID.xxx);
+        if (tengineID == TengineID.TIDBANK) {//银行卡
+            String cardNum = engine.TR_GetOCRFieldStringBuf(TFieldID.TBANK_NUM);
+            String bankName = engine.TR_GetOCRFieldStringBuf(TFieldID.TBANK_NAME);
+            String bankOrganizecode = engine.TR_GetOCRFieldStringBuf(TFieldID.TBANK_ORGCODE);
+            String bankCardclass = engine.TR_GetOCRFieldStringBuf(TFieldID.TBANK_CLASS);
+            String cardName = engine.TR_GetOCRFieldStringBuf(TFieldID.TBANK_CARD_NAME);
+            //银行卡引擎没有返回全部信息自己拼接
+            String allinfo = "银行卡号: " + cardNum + "\n"
+                    + "发卡行    : " + bankName + "\n"
+                    + "机构代码: " + bankOrganizecode + "\n"
+                    + "卡种         : " + bankCardclass + "\n"
+                    + "卡名         : " + cardName + "\n";
+            String jsonInfo = engine.TR_GetJsonStringBuffer();
+            info.setFieldString(TFieldID.TBANK_NUM, cardNum);
+            info.setFieldString(TFieldID.TBANK_NAME, bankName);
+            info.setFieldString(TFieldID.TBANK_ORGCODE, bankOrganizecode);
+            info.setFieldString(TFieldID.TBANK_CLASS, bankCardclass);
+            info.setFieldString(TFieldID.TBANK_CARD_NAME, bankName);
+            info.setAllinfo(allinfo);
+            info.setJsonInfo(jsonInfo);
+
+            /*银行卡小图*/
+            // 旧版引擎方法
+//            int x1 = engine.TR_GetLineRect(1);
+//            int y1 = engine.TR_GetLineRect(2);
+//            int x2 = engine.TR_GetLineRect(3);
+//            int y2 = engine.TR_GetLineRect(4);
+//            int isRotate180 = engine.TR_GetLineRect(0);//图片是否翻转标志,如果为 1 请自行将识别的图片旋转180度
+//            int w = x2 - x1;
+//            int h = y2 - y1;
+//            Bitmap smallBitmap = Bitmap.createBitmap(识别图片的bitmap, x1, y1, w, h);
+
+            // 新版引擎方法
+//            byte[] sdata = engine.TR_GetFieldImage(TFieldID.TBANK_IMG_STREAM);
+//            int size = engine.TR_GetFieldImageSize();
+//            if (size > 0 && sdata != null && sdata.length > 0) {
+//                Bitmap smallBitmap = BitmapFactory.decodeByteArray(sdata, 0, size);
+//            }
+
+        } else if (tengineID == TengineID.TIDCARD2) {//身份证
+            String num = engine.TR_GetOCRFieldStringBuf(TFieldID.NUM);
+            String name = engine.TR_GetOCRFieldStringBuf(TFieldID.NAME);
+            String bir = engine.TR_GetOCRFieldStringBuf(TFieldID.BIRTHDAY);
+            String address = engine.TR_GetOCRFieldStringBuf(TFieldID.ADDRESS);
+            String folk = engine.TR_GetOCRFieldStringBuf(TFieldID.FOLK);
+            String sex = engine.TR_GetOCRFieldStringBuf(TFieldID.SEX);
+            String passNum = engine.TR_GetOCRFieldStringBuf(TFieldID.IDC_PASS_NUM);
+            String period = engine.TR_GetOCRFieldStringBuf(TFieldID.PERIOD);
+            String issue = engine.TR_GetOCRFieldStringBuf(TFieldID.ISSUE);
+            TengineID IDCardType = engine.TR_GetCardType();  // TengineID.TIDCARD2 or TengineID.TIDCARDBACK
+            String allinfo = engine.TR_GetOCRStringBuf();
+            String jsonInfo = engine.TR_GetJsonStringBuffer();
+            info.setFieldString(TFieldID.NAME, name);
+            info.setFieldString(TFieldID.SEX, sex);
+            info.setFieldString(TFieldID.FOLK, folk);
+            info.setFieldString(TFieldID.BIRTHDAY, bir);
+            info.setFieldString(TFieldID.ADDRESS, address);
+            info.setFieldString(TFieldID.NUM, num);
+            info.setFieldString(TFieldID.IDC_PASS_NUM, passNum);
+            info.setFieldString(TFieldID.PERIOD, period);
+            info.setFieldString(TFieldID.ISSUE, issue);
+            info.setAllinfo(allinfo);
+            info.setJsonInfo(jsonInfo);
+
+//            int quality = engine.TR_GetParam(TParam.T_SET_EVALUE_QUALITY);
+
+            // 只有在识别身份证时候,才可以获取人头像信息。国徽面不会有信息。
+            // 有文字信息后才去读取头像
+            if (config.isOpenSmallPicture() && name != null && !"".equals(name)) {
+                byte[] hdata = engine.TR_GetHeadImgBuf();
+                int size = engine.TR_GetHeadImgBufSize();
+                if (size > 0 && hdata != null && hdata.length > 0) {
+                    Bitmap headImgBitmap = BitmapFactory.decodeByteArray(hdata, 0, size);
+                    if (headImgBitmap != null) {
+                        CameraActivity.smallBitmap = headImgBitmap.copy(Bitmap.Config.RGB_565, false);
+                    }
+                }
+            }
+            if ((config.isbMattingOfIdcard()) && ((name != null && !"".equals(name)) || (issue != null && !"".equals(issue)))) {
+                // 有信息后再抠图
+                byte[] cardByte = engine.TR_GetFieldImage(TFieldID.TR_FULL_IMAGE);
+                int sizex = engine.TR_GetFieldImageSize();
+                if (sizex > 0 && cardByte != null && cardByte.length > 0) {
+                    try {
+                        Bitmap bitmap = BitmapFactory.decodeByteArray(cardByte, 0, sizex);
+                        if (bitmap != null) {
+                            CameraActivity.takeBitmap = bitmap.copy(Bitmap.Config.RGB_565, false);
+                        }
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        } else if (tengineID == TengineID.TIDDOCUMENT) {
+            String text = engine.TR_GetOCRFieldStringBuf(TFieldID.DOC_TEXT);
+            String allInfo = engine.TR_GetOCRStringBuf();
+            String jsonInfo = engine.TR_GetJsonStringBuffer();
+            info.setFieldString(TFieldID.DOC_TEXT, text);
+            info.setAllinfo(allInfo);
+            info.setJsonInfo(jsonInfo);
+            byte[] cardByte = engine.TR_GetFieldImage(TFieldID.TR_FULL_IMAGE);
+            int sizex = engine.TR_GetFieldImageSize();
+            if (sizex > 0 && cardByte != null && cardByte.length > 0) {
+                try {
+                    // 截出来的图
+                    Bitmap bitmap = BitmapFactory.decodeByteArray(cardByte, 0, sizex);
+                    if (bitmap != null) {
+                        CameraActivity.takeBitmap = bitmap.copy(Bitmap.Config.RGB_565, false);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } else {
+            // 其它证件就不一一举例,特殊的只有身份证与银行卡已经写好了。
+            // 根据对应的字段获取单个栏目信息,获取其他信息,以此类推
+            String allinfo = engine.TR_GetOCRStringBuf(); // 获取所有识别信息
+            String jsonInfo = engine.TR_GetJsonStringBuffer();
+            info.setAllinfo(allinfo);
+            info.setJsonInfo(jsonInfo);
+//            engine.TR_GetOCRFieldStringBuf(TFieldID.xxx);
+            // 这里要补全自己需要的证件
+            // 这里要补全自己需要的证件
+            // 这里要补全自己需要的证件
+            // 这里要补全自己需要的证件
+            // 这里要补全自己需要的证件
+            // 这里要补全自己需要的证件
+        }
+
+        return info;
+    }
+
+}

+ 96 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/ImageUitl.java

@@ -0,0 +1,96 @@
+package cn.gxeea.zk;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Base64;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class ImageUitl {
+
+    /**
+     *
+     * @param image
+     * @param size kb
+     * @return
+     */
+    public static Bitmap comppress(Bitmap image,int size) {
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+        int options = 100;
+        while( baos.toByteArray().length>size*1024&&options>30){//判断如果图片大于,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
+            baos.reset();//重置baos即清空baos
+            options -= 10;//每次都减少10
+            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//把压缩后的数据存放到baos中
+        }
+        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
+        if (baos.toByteArray().length<=size*1024){
+            Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
+            image.recycle();
+            return bitmap;
+        }
+
+
+        BitmapFactory.Options newOpts = new BitmapFactory.Options();
+        //开始读入图片,此时把options.inJustDecodeBounds 设回true了
+        newOpts.inJustDecodeBounds = true;
+        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
+        newOpts.inJustDecodeBounds = false;
+        int be =size*1024/baos.toByteArray().length ;//be=1表示不缩放
+        newOpts.inSampleSize = be;//设置缩放比例
+        newOpts.inPreferredConfig = Bitmap.Config.RGB_565;//降低图片从ARGB888到RGB565
+        //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
+        isBm = new ByteArrayInputStream(baos.toByteArray());
+        bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
+        image.recycle();
+        return bitmap;//压缩好比例大小后再进行质量压缩
+    }
+
+    /**
+     * bitmap转为base64
+     * @param bitmap
+     * @return
+     */
+    public static String bitmapToBase64(Bitmap bitmap) {
+
+        String result = null;
+        ByteArrayOutputStream baos = null;
+        try {
+            if (bitmap != null) {
+                baos = new ByteArrayOutputStream();
+                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+
+                baos.flush();
+                baos.close();
+
+                byte[] bitmapBytes = baos.toByteArray();
+                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (baos != null) {
+                    baos.flush();
+                    baos.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * base64转为bitmap
+     * @param base64Data
+     * @return
+     */
+    public static Bitmap base64ToBitmap(String base64Data) {
+        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
+        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+    }
+}

+ 30 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/IntegerUtil.java

@@ -0,0 +1,30 @@
+package cn.gxeea.zk;
+
+import java.util.List;
+
+/**
+ * Created on 22/07/2018.
+ *
+ * @author Qiang Lili.
+ */
+public class IntegerUtil {
+    /**
+     * Get int array from integer list.
+     *
+     * @param integerList integer list.
+     * @return int array.
+     */
+    public static int[] convertIntegerListToArray(final List<Integer> integerList) {
+        if (integerList.isEmpty()) {
+            return new int[0];
+        }
+
+        int[] array = new int[integerList.size()];
+
+        for (int index = 0; index < integerList.size(); index++) {
+            array[index] = integerList.get(index);
+        }
+
+        return array;
+    }
+}

+ 111 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/LivenessModule.java

@@ -0,0 +1,111 @@
+package cn.gxeea.zk;
+
+import android.app.Activity;
+import android.content.Intent;
+import com.alibaba.fastjson.JSONObject;
+import com.turui.liveness.motion.MotionLivenessActivity;
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+
+public class LivenessModule extends UniModule {
+
+    String TAG = "LivenessModule";
+    public static int REQUEST_CODE = 600;
+
+    UniJSCallback _callback;
+
+
+//    //run ui thread
+//    @UniJSMethod(uiThread = true)
+//    public void testAsyncFunc(JSONObject options, UniJSCallback callback) {
+//        Log.e(TAG, "testAsyncFunc--" + options);
+//        if (callback != null) {
+//            JSONObject data = new JSONObject();
+//            data.put("code", "success");
+//            callback.invoke(data);
+//            //callback.invokeAndKeepAlive(data);
+//        }
+//    }
+//
+//    //run JS thread
+//    @UniJSMethod(uiThread = false)
+//    public JSONObject testSyncFunc() {
+//        JSONObject data = new JSONObject();
+//        data.put("code", "success");
+//        return data;
+//    }
+
+    public static final int RESULT_OK = -1;
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        switch (resultCode) {
+            case 0:
+            case MotionLivenessActivity.CANCEL_INITIATIVE:
+                //_callback.invoke("活体检测取消");
+                break;
+            default:
+                if (intent != null && !intent.getBooleanExtra(
+                        MotionLivenessActivity.RESULT_DEAL_ERROR_INNER, false)) {
+                    if(resultCode==RESULT_OK){
+//                        AsyncTask.execute(() -> {
+//                            List<byte[]> ls = ImageManager.getInstance().getCroppedImages();
+//                            try {
+//                                URL url = new URL(urlString);
+//                                CompareResult result = new CompareResult();
+//                                FaceEngineUtils.faceBitmapCompare(BitmapFactory.decodeStream(url.openStream()), BitmapFactory.decodeByteArray(ls.get(0), 0, ls.get(0).length), result);
+//                                mUniSDKInstance.runOnUiThread(() -> {
+//                                    if (result.errorCode == 0 && result.similar > 80) {
+//                                        _callback.invoke("success");
+//                                    } else
+//                                        _callback.invoke("活体检测失败");
+//                                });
+//
+//                            } catch (IOException e) {
+//                                e.printStackTrace();
+//                                mUniSDKInstance.runOnUiThread(() -> {
+//                                    _callback.invoke("活体检测失败");
+//                                });
+//
+//                            }
+//                        });
+                        _callback.invoke("success");
+
+                    }else{
+                        _callback.invoke("活体检测失败");
+                    }
+                }else{
+                    _callback.invoke("活体检测失败");
+                }
+                break;
+        }
+    }
+
+    static {
+        try {
+            System.loadLibrary("nllvm1623613607");
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+        }
+    }
+    String urlString;
+
+    @UniJSMethod(uiThread = true)
+    public void liveness(JSONObject options, UniJSCallback callback) {
+        _callback = callback;
+        urlString = options.getString("url");
+        if (mUniSDKInstance != null && mUniSDKInstance.getContext() instanceof Activity) {
+            PreferenceUtil.init(mUniSDKInstance.getContext());
+            Intent intent = new Intent(mUniSDKInstance.getContext(), MotionLivenessActivity.class);
+            intent.putExtra(MotionLivenessActivity.EXTRA_DIFFICULTY, PreferenceUtil.getDifficulty());
+            intent.putExtra(MotionLivenessActivity.EXTRA_VOICE,
+                    PreferenceUtil.isInteractiveLivenessVoiceOn());
+            intent.putExtra(MotionLivenessActivity.EXTRA_SEQUENCES, PreferenceUtil.getSequence());
+
+            ((Activity) mUniSDKInstance.getContext()).startActivityForResult(intent, REQUEST_CODE);
+
+        }
+    }
+
+}

+ 49 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/MyApplication.java

@@ -0,0 +1,49 @@
+package cn.gxeea.zk;
+
+import android.content.Context;
+import androidx.multidex.MultiDex;
+import io.dcloud.application.DCloudApplication;
+
+import java.lang.ref.WeakReference;
+//import org.opencv.OpencvHelper;
+
+public class MyApplication extends DCloudApplication {
+    private static WeakReference<Context> context;//全局的上下文
+    @Override
+    protected void attachBaseContext(Context base) {
+        super.attachBaseContext(base);
+        MultiDex.install(base);
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        context = new WeakReference<Context>(this);
+//        CrashReport.initCrashReport(getApplicationContext(), "cee40cc5bb", true);
+//        SharePreferenceObj.getInstance().init(this, getPackageName() + ".spData");
+//        SoundPoolManager.getInstance().register(this);
+//        //CameraConfigUtils.init(1);
+//        Camera1ParamsManager.getInstance().init(this, "defaultCamera1ParamsV0001.json");
+//        LocalFileManager.getInstance().init(this);
+//        OpencvHelper.init();
+//        new Thread(() -> {
+//            FaceEngineUtils.initFacePPApi(getApplicationContext());
+//            FaceEngineUtils.initFaceDetectApi();
+//            FaceEngineUtils.defaultFaceConfidenceFilter();
+//            FaceEngineUtils.initBodySegment(1);
+//        }).start();
+    }
+
+    @Override
+    public void onTerminate() {
+        super.onTerminate();
+//        SoundPoolManager.getInstance().unregister();
+//        FaceEngineUtils.releaseBodySegment();
+//        FaceEngineUtils.releaseFaceDetectApi();
+//        FaceEngineUtils.releaseFacePPApi();
+    }
+
+    public static Context getContext(){
+        return context.get();
+    }
+}

+ 257 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/OcrDocModule.java

@@ -0,0 +1,257 @@
+
+
+package cn.gxeea.zk;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+import com.alibaba.fastjson.JSONObject;
+import com.idcard.*;
+import com.turui.android.activity.WCameraActivity;
+import com.turui.engine.EngineConfig;
+import com.turui.engine.InfoCollection;
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+public class OcrDocModule extends UniModule {
+
+    String TAG = "OcrDocModule";
+    public static int REQUEST_CODE = 600;
+
+    protected TRECAPI engine;  // 引擎
+
+    UniJSCallback _callback;
+    Integer resultType;
+
+//    //run ui thread
+//    @UniJSMethod(uiThread = true)
+//    public void testAsyncFunc(JSONObject options, UniJSCallback callback) {
+//        Log.e(TAG, "testAsyncFunc--" + options);
+//        if (callback != null) {
+//            JSONObject data = new JSONObject();
+//            data.put("code", "success");
+//            callback.invoke(data);
+//            //callback.invokeAndKeepAlive(data);
+//        }
+//    }
+//
+//    //run JS thread
+//    @UniJSMethod(uiThread = false)
+//    public JSONObject testSyncFunc() {
+//        JSONObject data = new JSONObject();
+//        data.put("code", "success");
+//        return data;
+//    }
+
+//    @Override
+//    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+//        if (requestCode == REQUEST_CODE && data.hasExtra("respond")) {
+//            Log.e("TestModule", "原生页面返回----" + data.getStringExtra("respond"));
+//        } else {
+//            super.onActivityResult(requestCode, resultCode, data);
+//        }
+//    }
+
+    /**
+     * 获取识别结果
+     *
+     * @param requestCode
+     * @param resultCode
+     * @param data
+     */
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == 600 && resultCode == 601 && data != null) {
+            Bundle bundle = data.getExtras();
+
+            if (resultType == 1) {
+
+                InfoCollection info = (InfoCollection) bundle.getSerializable("info");
+//                if (info != null) {
+//                    Toast.makeText(mUniSDKInstance.getContext(), (info.getAllinfo() == null ? "(没有文本信息)" : info.getAllinfo()), Toast.LENGTH_SHORT).show();
+//                }
+                JSONObject json = new JSONObject();
+                json.put("NAME", info.getFieldString(TFieldID.NAME));
+                json.put("NUM", info.getFieldString(TFieldID.NUM));
+                _callback.invoke(json);
+            }
+            /**
+             演示显示全部信息,开发过程可以获取单个信息
+             使用 TFieldID 枚举即可
+             info.getFieldString(TFieldID.NAME);// 姓名
+             info.getFieldString(TFieldID.SEX);// 性别
+             info.getFieldString(TFieldID.ISSUE);// 签发机关
+             ... ...
+             */
+            //图片内部没有进行回收,获取图片后请自行保存,以免出现问题
+
+            /**获取图片*/
+            if (resultType == 2) {
+                Bitmap.Config config = Bitmap.Config.RGB_565;
+                //全图
+                if (WCameraActivity.takeBitmap != null && !WCameraActivity.takeBitmap.isRecycled()) {
+                    Bitmap bitmap = WCameraActivity.takeBitmap.copy(config, true);
+                    if (bitmap != null) {
+                        //建立指定文件夹
+                        File foder = new File(mUniSDKInstance.getContext().getExternalCacheDir(), "ocr_doc");
+                        if (!foder.exists()) {
+                            foder.mkdirs();
+                        }
+                        File myCaptureFile = new File(foder, System.currentTimeMillis() + ".jpg");
+                        try {
+                            if (!myCaptureFile.exists()) {
+                                myCaptureFile.createNewFile();
+                            }
+                            Matrix m = new Matrix();
+                            m.setRotate(-90, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
+
+                            Bitmap bm1 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
+                            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile));
+                            //压缩保存到本地
+                            bm1.compress(Bitmap.CompressFormat.JPEG, 100, bos);
+                            bos.flush();
+                            bos.close();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+//                        Log.i(TAG, bitmapToBase64(bitmap));
+//                        _callback.invoke(bitmapToBase64(comppress(bitmap,150)));
+                        //_callback.invoke(bitmapToBase64(bitmap));
+                        _callback.invoke(myCaptureFile.getAbsolutePath());
+                        bitmap.recycle();
+                    } else {
+                        Toast.makeText(mUniSDKInstance.getContext(), "图片为null", Toast.LENGTH_SHORT).show();
+                    }
+                } else {
+                }
+            }
+//            if (engine != null) {
+//                engine.TR_ClearUP();//释放内存===============<<<<<<<<<<<<<<<<<<<<<这里
+//            }
+        } else {
+        }
+        if (data == null) {
+            //Toast.makeText(mUniSDKInstance.getContext(), "Data为空", Toast.LENGTH_SHORT).show();
+            Toast.makeText(mUniSDKInstance.getContext(), "取消操作", Toast.LENGTH_SHORT).show();
+        }
+        if (engine != null) {
+            engine.TR_ClearUP();//释放内存===============<<<<<<<<<<<<<<<<<<<<<这里
+        }
+
+    }
+
+    //    docType:"idcard",
+//    resultType:1
+//    docType: "doc",
+//    resultType: 2
+    @UniJSMethod(uiThread = true)
+    public void ocrDoc(JSONObject options, UniJSCallback callback) {
+        _callback = callback;
+        resultType = options.getInteger("resultType");
+        String docType = options.getString("docType");
+        initEngine();
+        if (mUniSDKInstance != null && mUniSDKInstance.getContext() instanceof Activity) {
+            if (docType.equals("idcard")) {
+                // 请认真检查初始化方法 engine.TR_StartUP(var1, var2) 的返回值是否成功否则进入识别界面将无法正常运行!!!*/
+                Intent intent = new Intent(mUniSDKInstance.getContext(), WCameraActivity.class);
+                EngineConfig config = new EngineConfig(engine, TengineID.TIDCARD2);
+                config.setEngingModeType(EngineConfig.EngingModeType.SCAN);//拍照模式
+                config.setShowModeChange(true);//是否显示模式切换按钮
+                config.setbMattingOfIdcard(true);//正常下不用,引擎内部裁切外部无法调整,跟so挂钩,安卓层无法调整
+                config.setCheckCopyOfIdcard(false);//正常下不用,翻拍检测,在结果中获取InfoCollection类中的 .getImageProperty()
+                config.setOpenSmallPicture(true);//开启小图(身份证头像与银行卡卡号其它证件没有)
+                config.setDecodeInRectOfTakeMode(false);//UI层裁切,无法非常准确。只有拍照模式支持
+                config.setSaveToData(false);//保存到私有目录
+                config.setResultCode(601);
+                config.setHideVersionTip(true);//强制关闭测试版提示
+                config.setMakeMeasureSpec(true);
+                config.setShowAboutInfo(false);//仅在测试时使用
+                config.setLogcatEnable(false);//是否在控制台打印log
+                config.setLogcatSaveToFile(false);//日志是否保存到文件
+//                config.setLogSaveToFilePath(path);//日志保存路径,自行确认路径与权限
+//                config.setMaximumWidth(3000);  // 最大拍照分辨率
+//                config.setMaximumPreviewWidth(3000);  // 最大扫描分辨率
+                config.setForceCamera1(true);  // 是否强制使用Camera1接口
+                intent.putExtra(EngineConfig.class.getSimpleName(), config);//必须有
+                ((Activity) mUniSDKInstance.getContext()).startActivityForResult(intent, REQUEST_CODE);
+            } else if (docType.equals("doc")) {
+                // 请认真检查初始化方法 engine.TR_StartUP(var1, var2) 的返回值是否成功否则进入识别界面将无法正常运行!!!*/
+                Intent intent = new Intent(mUniSDKInstance.getContext(), SimpleCustomUIDocActivity.class);
+//                Intent intent = new Intent(mUniSDKInstance.getContext(), WCameraActivity.class);
+                EngineConfig config = new EngineConfig(engine, TengineID.TIDDOCUMENT);
+                config.setEngingModeType(EngineConfig.EngingModeType.TAKE);//拍照模式
+                config.setShowModeChange(true);//是否显示模式切换按钮
+                config.setbMattingOfIdcard(true);//正常下不用,引擎内部裁切外部无法调整,跟so挂钩,安卓层无法调整
+                config.setCheckCopyOfIdcard(false);//正常下不用,翻拍检测,在结果中获取InfoCollection类中的 .getImageProperty()
+                config.setOpenSmallPicture(true);//开启小图(身份证头像与银行卡卡号其它证件没有)
+                config.setDecodeInRectOfTakeMode(false);//UI层裁切,无法非常准确。只有拍照模式支持
+                config.setSaveToData(false);//保存到私有目录
+                config.setResultCode(601);
+                config.setHideVersionTip(true);//强制关闭测试版提示
+                config.setMakeMeasureSpec(true);
+                config.setShowAboutInfo(false);//仅在测试时使用
+                config.setLogcatEnable(true);//是否在控制台打印log
+                config.setLogcatSaveToFile(false);//日志是否保存到文件
+//                config.setLogSaveToFilePath(path);//日志保存路径,自行确认路径与权限
+//                config.setMaximumWidth(3000);  // 最大拍照分辨率
+//                config.setMaximumPreviewWidth(3000);  // 最大扫描分辨率
+                config.setForceCamera1(true);  // 是否强制使用Camera1接口
+                intent.putExtra(EngineConfig.class.getSimpleName(), config);//必须有
+                ((Activity) mUniSDKInstance.getContext()).startActivityForResult(intent, REQUEST_CODE);
+            }
+
+
+        }
+    }
+
+//    @Override
+//    public void onActivityStart() {
+//        super.onActivityStart();
+//        initEngine();
+//    }
+
+
+    /**
+     * 初始化引擎
+     */
+    protected void initEngine() {
+        // 如果这里发生异常请检查libs下是不是有对应的 .so 文件
+        try {
+
+            // 集成时注意对应的so架构文件不要弄混,并且检查配置文件
+            engine = new TRECAPIImpl();
+            TStatus initStatus = engine.TR_StartUP(mUniSDKInstance.getContext(), engine.TR_GetEngineTimeKey());
+            if (null == initStatus || initStatus != TStatus.TR_OK) {
+                Toast.makeText(mUniSDKInstance.getContext(), "引擎初始化失败", Toast.LENGTH_SHORT).show();
+            }
+            Log.d("[ocr]", engine.TR_GetUseTimeString());
+            Log.d("[ocr]", engine.TR_GetCopyrightInfo());
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+
+        }
+    }
+
+    @Override
+    public void onActivityCreate() {
+        super.onActivityCreate();
+    }
+
+    @Override
+    public void onActivityDestroy() {
+        super.onActivityDestroy();
+    }
+}
+

+ 55 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/PayModule.java

@@ -0,0 +1,55 @@
+package cn.gxeea.zk;
+
+import com.alibaba.fastjson.JSONObject;
+import com.chinaums.pppay.unify.UnifyPayPlugin;
+import com.chinaums.pppay.unify.UnifyPayRequest;
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+public class PayModule extends UniModule {
+
+
+    String TAG = "PayModule";
+
+
+//    //run ui thread
+//    @UniJSMethod(uiThread = true)
+//    public void testAsyncFunc(JSONObject options, UniJSCallback callback) {
+//        Log.e(TAG, "testAsyncFunc--" + options);
+//        if (callback != null) {
+//            JSONObject data = new JSONObject();
+//            data.put("code", "success");
+//            callback.invoke(data);
+//            //callback.invokeAndKeepAlive(data);
+//        }
+//    }
+//
+//    //run JS thread
+//    @UniJSMethod(uiThread = false)
+//    public JSONObject testSyncFunc() {
+//        JSONObject data = new JSONObject();
+//        data.put("code", "success");
+//        return data;
+//    }
+
+
+
+    @UniJSMethod(uiThread = true)
+    public void pay(JSONObject options, UniJSCallback callback) {
+        int payMode = options.getInteger("payMode");
+        String orderInfo = options.getString("orderInfo");
+        UnifyPayRequest msg=new UnifyPayRequest();
+        msg.payData=orderInfo;
+        if (payMode == 1) {//wxpays
+            msg.payChannel=UnifyPayRequest.CHANNEL_WEIXIN;
+        } else if (payMode == 2) {//alipays
+            msg.payChannel=UnifyPayRequest.CHANNEL_ALIPAY_MINI_PROGRAM;
+        }
+        UnifyPayPlugin.getInstance(mUniSDKInstance.getContext()).sendPayRequest(msg);
+        JSONObject data = new JSONObject();
+        data.put("code", "success");
+        callback.invoke(data);
+    }
+
+}

+ 226 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/PreferenceUtil.java

@@ -0,0 +1,226 @@
+package cn.gxeea.zk;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import com.sensetime.senseid.sdk.liveness.interactive.MotionComplexity;
+import com.sensetime.senseid.sdk.liveness.interactive.NativeMotion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created on 2017/03/09 13:39.
+ *
+ * @author Han Xu
+ */
+public final class PreferenceUtil {
+
+    private static final String KEY_COMPARE_MODE_VOICE = "key_compare_mode_voice";
+    private static final String KEY_INTERACTIVE_LIVENESS_VOICE = "key_interactive_liveness_voice";
+    private static final String KEY_DIFFICULTY = "key_difficulty";
+    private static final String KEY_RANDOM_SEQUENCE = "key_random_sequence";
+    private static final String KEY_SAVE_INTERACTIVE_IMAGE = "key_save_interactive_image";
+    private static final String KEY_SAVE_SILENT_IMAGE = "key_save_silent_image";
+    private static final String KEY_SEQUENCE = "key_sequence";
+
+    private static final char SEPARATOR = ';';
+
+    private static final String DEFAULT_SEQUENCE = String.valueOf(NativeMotion.CV_LIVENESS_BLINK)
+            + SEPARATOR
+            + NativeMotion.CV_LIVENESS_MOUTH
+            + SEPARATOR
+            + NativeMotion.CV_LIVENESS_HEADNOD
+            + SEPARATOR
+            + NativeMotion.CV_LIVENESS_HEADYAW;
+
+    private static SharedPreferences mPreferences = null;
+
+    public static void init(Context context) {
+        mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+    }
+
+    public static boolean isCompareModeVoiceOn() {
+        return mPreferences.getBoolean(KEY_COMPARE_MODE_VOICE, true);
+    }
+
+    public static boolean isInteractiveLivenessVoiceOn() {
+        return mPreferences.getBoolean(KEY_INTERACTIVE_LIVENESS_VOICE, true);
+    }
+
+    public static void setIsCompareModeVoiceOn(boolean isVoiceOn) {
+        mPreferences.edit().putBoolean(KEY_COMPARE_MODE_VOICE, isVoiceOn).apply();
+    }
+
+    /**
+     * 获取难度设置.
+     *
+     * @return 动作活体检测的难度
+     */
+    public static int getDifficultyTextId() {
+        int difficulty = mPreferences.getInt(KEY_DIFFICULTY, MotionComplexity.NORMAL);
+        switch (difficulty) {
+//            case MotionComplexity.EASY:
+//                return R.string.easy;
+//            case MotionComplexity.HARD:
+//                return R.string.hard;
+//            case MotionComplexity.HELL:
+//                return R.string.hell;
+//            case MotionComplexity.NORMAL:
+//                return R.string.normal;
+            default:
+                return -1;
+        }
+    }
+
+    public static boolean isRandomSequenceOn() {
+        return mPreferences.getBoolean(KEY_RANDOM_SEQUENCE, true);
+    }
+
+    public static void setIsRandomSequenceOn(boolean isOn) {
+        mPreferences.edit().putBoolean(KEY_RANDOM_SEQUENCE, isOn).apply();
+    }
+
+    public static void setIsInteractiveLivenessVoiceOn(boolean isOn) {
+        mPreferences.edit().putBoolean(KEY_INTERACTIVE_LIVENESS_VOICE, isOn).apply();
+    }
+
+    public static boolean isSaveInteractiveImage() {
+        return mPreferences.getBoolean(KEY_SAVE_INTERACTIVE_IMAGE, false);
+    }
+
+    public static boolean isSaveSilentImage() {
+        return mPreferences.getBoolean(KEY_SAVE_SILENT_IMAGE, false);
+    }
+
+    public static void setIsSaveInteractiveImageEnable(boolean isEnable) {
+        mPreferences.edit().putBoolean(KEY_SAVE_INTERACTIVE_IMAGE, isEnable).apply();
+    }
+
+    public static void setIsSaveSilentImageEnable(boolean isEnable) {
+        mPreferences.edit().putBoolean(KEY_SAVE_SILENT_IMAGE, isEnable).apply();
+    }
+
+    public static int getDifficulty() {
+        return mPreferences.getInt(KEY_DIFFICULTY, MotionComplexity.NORMAL);
+    }
+
+    public static void setDifficulty(int difficulty) {
+        mPreferences.edit().putInt(KEY_DIFFICULTY, difficulty).apply();
+    }
+
+    public static int[] getSequence() {
+        return splitWorker(mPreferences.getString(KEY_SEQUENCE, DEFAULT_SEQUENCE), SEPARATOR, false);
+    }
+
+    public static void setSequence(int[] sequence) {
+        mPreferences.edit().putString(KEY_SEQUENCE, join(sequence, SEPARATOR, 0, sequence.length)).apply();
+    }
+
+    public static List<Integer> getSequenceList() {
+        return splitStringWorker(mPreferences.getString(KEY_SEQUENCE, DEFAULT_SEQUENCE), SEPARATOR, false);
+    }
+
+    /**
+     * Save new selected motion sequence.
+     *
+     * @param sequence Motion sequence.
+     */
+    public static void setSequenceList(List<Integer> sequence) {
+        mPreferences.edit()
+                .putString(KEY_SEQUENCE,
+                        join(IntegerUtil.convertIntegerListToArray(sequence), SEPARATOR, 0, sequence.size()))
+                .apply();
+    }
+
+    private static String join(int[] array, char separator, int startIndex, int endIndex) {
+        if (array == null) {
+            return null;
+        }
+        int bufSize = (endIndex - startIndex);
+        if (bufSize <= 0) {
+            return "";
+        }
+
+        bufSize *= ((String.valueOf(array[startIndex]).length()) + 1);
+        StringBuilder buf = new StringBuilder(bufSize);
+
+        for (int i = startIndex; i < endIndex; i++) {
+            if (i > startIndex) {
+                buf.append(separator);
+            }
+            buf.append(array[i]);
+        }
+        return buf.toString();
+    }
+
+    private static int[] splitWorker(String str, char separatorChar, boolean presserveAllTokens) {
+        if (str == null) {
+            return null;
+        }
+        int len = str.length();
+        if (len == 0) {
+            return new int[0];
+        }
+        List<Integer> list = new ArrayList<>();
+        int i = 0;
+        int start = 0;
+        boolean match = false;
+        boolean lastMatch = false;
+        while (i < len) {
+            if (str.charAt(i) == separatorChar) {
+                if (match || presserveAllTokens) {
+                    list.add(Integer.parseInt(str.substring(start, i)));
+                    match = false;
+                    lastMatch = true;
+                }
+                start = ++i;
+                continue;
+            }
+            lastMatch = false;
+            match = true;
+            i++;
+        }
+        if (match || (presserveAllTokens && lastMatch)) {
+            list.add(Integer.parseInt(str.substring(start, i)));
+        }
+        int[] result = new int[list.size()];
+        for (int index = 0; index < list.size(); index++) {
+            result[index] = list.get(index);
+        }
+        return result;
+    }
+
+    private static List<Integer> splitStringWorker(String str, char separatorChar, boolean presserveAllTokens) {
+        if (str == null) {
+            return null;
+        }
+        int len = str.length();
+        if (len == 0) {
+            return new ArrayList<>();
+        }
+        List<Integer> list = new ArrayList<>();
+        int i = 0;
+        int start = 0;
+        boolean match = false;
+        boolean lastMatch = false;
+        while (i < len) {
+            if (str.charAt(i) == separatorChar) {
+                if (match || presserveAllTokens) {
+                    list.add(Integer.parseInt(str.substring(start, i)));
+                    match = false;
+                    lastMatch = true;
+                }
+                start = ++i;
+                continue;
+            }
+            lastMatch = false;
+            match = true;
+            i++;
+        }
+        if (match || (presserveAllTokens && lastMatch)) {
+            list.add(Integer.parseInt(str.substring(start, i)));
+        }
+        return list;
+    }
+}

+ 437 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/SimpleCustomUIDocActivity.java

@@ -0,0 +1,437 @@
+package cn.gxeea.zk;
+
+import android.annotation.SuppressLint;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.database.Cursor;
+import android.graphics.*;
+import android.graphics.pdf.PdfDocument;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.print.PrintAttributes;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import androidx.annotation.RequiresApi;
+import com.idcard.TRECAPI;
+import com.idcard.TengineID;
+import com.turui.android.activity.CameraActivity;
+import com.turui.android.cameraview.FinderView;
+import com.turui.engine.EngineConfig;
+import com.turui.engine.InfoCollection;
+
+import java.io.*;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SimpleCustomUIDocActivity extends CameraActivity {
+
+    protected ImageView btnPhoto;
+
+    @Override
+    protected int getCustomContentView() {
+        //已经有的控件名称等不能变,增加自己要的控件并处理就行了,除非全部自定义
+        return R.layout.activity_simple_customui_doc;
+    }
+
+    @Override
+    protected void initBaseView() {
+        super.initBaseView();//调用默认的控件设置等
+
+        btnPhoto = findViewById(R.id.btn_custom_view_photo);
+        btnPhoto.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (curMode != EngineConfig.EngingModeType.TAKE) {
+                    changeMode();
+                }
+                openPhotos();
+            }
+        });
+    }
+
+    /**
+     * 成功,并不是指一定有数据,只是流程上的成功,还是要检查识别结果
+     *
+     * @param mode
+     * @param infoCollection
+     * @return 是否结束
+     */
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    protected boolean decodeSuccess(EngineConfig.EngingModeType mode, InfoCollection infoCollection) {
+        List<Bitmap> bitmaps = new ArrayList<>();
+        bitmaps.add(takeBitmap);
+        saveBitmapForPdf(bitmaps, Environment.getExternalStorageDirectory().getPath(), "take.pdf");
+
+        if (mode == EngineConfig.EngingModeType.TAKE && this.tipDialog != null && this.tipDialog.isShowing()) {
+            this.tipDialog.dismiss();
+        }
+
+        Intent intent = this.getIntent();
+        Bundle bundle = new Bundle();
+        bundle.putSerializable("info", infoCollection);
+        intent.putExtras(bundle);
+        this.setResult(this.engineConfig.getResultCode(), intent);
+        this.finish();
+
+//        restartDelayMS(300);
+//        return false;
+
+        return true;
+    }
+
+    /**
+     * bitmap to pdf
+     * @param bitmaps 需要保存的图片
+     * @param appDir 保存的路径
+     * @param name 保存的文件名
+     * @return
+     */
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    public File saveBitmapForPdf(List<Bitmap> bitmaps, String appDir, String name) {
+        PdfDocument doc = new PdfDocument();
+        int pageWidth = PrintAttributes.MediaSize.ISO_A4.getWidthMils() * 72 / 1000;
+
+        float scale = (float) pageWidth / (float) bitmaps.get(0).getWidth();
+        int pageHeight = (int) (bitmaps.get(0).getHeight() * scale);
+
+        Matrix matrix = new Matrix();
+        matrix.postScale(scale, scale);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        for (int i = 0; i < bitmaps.size(); i++) {
+            PdfDocument.PageInfo newPage = new PdfDocument.PageInfo.Builder(pageWidth, pageHeight, i).create();
+            PdfDocument.Page page = doc.startPage(newPage);
+            Canvas canvas = page.getCanvas();
+            canvas.drawBitmap(bitmaps.get(i), matrix, paint);
+            doc.finishPage(page);
+        }
+        File file = new File(appDir, name);
+        FileOutputStream outputStream = null;
+        try {
+            outputStream = new FileOutputStream(file);
+            doc.writeTo(outputStream);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            doc.close();
+            try {
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return file;
+    }
+
+    /**
+     * 失败
+     *
+     * @param mode
+     * @return 是否结束
+     */
+    protected boolean decodeFail(EngineConfig.EngingModeType mode) {
+        if (mode == EngineConfig.EngingModeType.TAKE && this.tipDialog != null && this.tipDialog.isShowing()) {
+            this.tipDialog.dismiss();
+        }
+
+        if (mode == EngineConfig.EngingModeType.TAKE) {
+            this.finish();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 异常
+     *
+     * @param mode
+     * @param e
+     */
+    protected void decodeException(EngineConfig.EngingModeType mode, Exception e) {
+        Log.d("ocr", "异常");
+    }
+
+    /**
+     * 打开相册
+     */
+    @SuppressLint("IntentReset")
+    private void openPhotos() {
+        Intent intent = new Intent();
+        intent.setType("image/*");
+//        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,false);
+        intent.setAction(Intent.ACTION_PICK);
+        intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); //直接打开系统相册  不设置会有选择相册一步(例:系统相册、QQ浏览器相册)
+        startActivityForResult(Intent.createChooser(intent, "Select Picture"), 200);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (resultCode == RESULT_OK && requestCode == 200 && data != null) {
+            String path = null;
+            if (Build.VERSION.SDK_INT >= 19) {
+                path = handImage(data);
+            } else {
+                path = handImageLow(data);
+            }
+            if (path != null) {
+                tipDialog.show();
+                //未识别结束时请不要使用扫描或点击拍照,具体逻辑请自行处理。
+                final String finalPath = path;
+                new Thread(new Runnable() {
+                    @Override
+                    public void run() {
+                        //该类只实现银行卡/身份证/Doc,其它证件请自行补充
+                        decodePhoto(engineConfig, finalPath, engine, tengineID);
+                    }
+                }, "WCameraPhotoActivityDecode").start();
+            }
+        }
+
+    }
+
+    /**
+     * 高版本获取路径
+     * @param data
+     * @return
+     */
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    private String handImage(Intent data) {
+        String path = null;
+        Uri uri = data.getData();
+        //根据不同的uri进行不同的解析
+        if (DocumentsContract.isDocumentUri(this, uri)) {
+            String docId = DocumentsContract.getDocumentId(uri);
+            if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
+                String id = docId.split(":")[1];
+                String selection = MediaStore.Images.Media._ID + "=" + id;
+                path = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
+            } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
+                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
+                path = getImagePath(contentUri, null);
+            }
+        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
+            path = getImagePath(uri, null);
+        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+            path = uri.getPath();
+        }
+        return path;
+    }
+
+    /**
+     * 低版本获取路径
+     * @param data
+     * @return
+     */
+    private String handImageLow(Intent data) {
+        Uri uri = data.getData();
+        return getImagePath(uri, null);
+    }
+
+    /**
+     * 获取路径
+     * @param uri
+     * @param selection
+     * @return
+     */
+    private String getImagePath(Uri uri, String selection) {
+        String path = null;
+        Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
+            }
+            cursor.close();
+        }
+        return path;
+    }
+
+    /**
+     * 识别图片
+     * @param config
+     * @param path
+     * @param engine
+     * @param tengineID
+     */
+    protected void decodePhoto(EngineConfig config, String path, TRECAPI engine, TengineID tengineID) {
+//        InfoCollection info = CustomOcrDecode.decodeByPath(path, engine, tengineID);
+//        decodeSuccess(EngineConfig.EngingModeType.TAKE, info);
+
+        FileInputStream fis = null;
+        InfoCollection info = null;
+        try {
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inPreferredConfig = Bitmap.Config.RGB_565;
+            options.inMutable = false;
+            fis = new FileInputStream(path);
+            Bitmap bitmap = BitmapFactory.decodeStream(fis);
+            fis.close();
+
+            fis = new FileInputStream(path);// 重新设置流,防止无数据
+            bitmap = BitmapFactory.decodeStream(fis, new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()), options);
+            fis.close();
+
+            takeBitmap = bitmap;
+            info = CustomOcrDecode.decodeByBitmap(config, bitmap, engine, tengineID);
+//            info = CustomOcrDecode.decodeByPath(config, path, engine, tengineID);
+            final InfoCollection finalInfo = info;
+            runOnUiThread(new Runnable() {
+                @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+                @Override
+                public void run() {
+                    decodeSuccess(EngineConfig.EngingModeType.TAKE, finalInfo);
+                }
+            });
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    /**
+     * 获取屏幕分辨率
+     *
+     * @param context context
+     * @return
+     */
+    public Point getScreenResolution(Context context) {
+//        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+//        Display display = wm.getDefaultDisplay();
+//        Point screenResolution = new Point();
+//        if (android.os.Build.VERSION.SDK_INT >= 13) {
+//            display.getSize(screenResolution);
+//        } else {
+//            screenResolution.set(display.getWidth(), display.getHeight());
+//        }
+//        return screenResolution;
+
+        //反射获取
+        Point point = new Point();
+        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = windowManager.getDefaultDisplay();
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        @SuppressWarnings("rawtypes")
+        Class c;
+        try {
+            c = Class.forName("android.view.Display");
+            @SuppressWarnings("unchecked")
+            Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
+            method.invoke(display, displayMetrics);
+            point.x = displayMetrics.widthPixels;
+            point.y = displayMetrics.heightPixels;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return point;
+    }
+
+    /**
+     * 修改框大小
+     *
+     * @param finderView
+     * @return
+     */
+    protected FinderView setWidthHeight(FinderView finderView) {
+        int pbw = 66;
+        if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+            Point size = getScreenResolution(this);
+            int max = Math.max(size.x, size.y);
+            int min = Math.min(size.x, size.y);
+            float ratio = (float) max / min;
+            if (ratio > 1.78f) {//在长屏幕机型有可能会太高,改小点
+                pbw = 60;
+            }
+        }
+        if (tengineID == TengineID.TIDLPR) {
+            finderView.setRectCenterXPercent(50);
+            finderView.setRectCenterYPercent(60);
+            finderView.setRectPortraitWidthPercent(60);
+            finderView.setRectPortraitHeightPercentByWidth(55);
+            finderView.setRectLandscapeWidthPercent(35);
+            finderView.setRectLandscapeHeightPercentByWidth(55);
+        } else if (tengineID == TengineID.TIDJSZCARD) {
+            finderView.setRectPortraitWidthPercent(85);
+            finderView.setRectPortraitHeightPercentByWidth(67);
+            finderView.setRectLandscapeWidthPercent(pbw + 2);
+            finderView.setRectLandscapeHeightPercentByWidth(67);
+        } else if (tengineID == TengineID.TIDXSZCARD) {
+            finderView.setRectPortraitWidthPercent(85);
+            finderView.setRectPortraitHeightPercentByWidth(67);
+            finderView.setRectLandscapeWidthPercent(pbw + 2);
+            finderView.setRectLandscapeHeightPercentByWidth(67);
+        } else if (tengineID == TengineID.TIDBIZLIC) {
+            finderView.setRectCenterXPercent(50);
+            finderView.setRectCenterYPercent(50);
+            finderView.setRectPortraitWidthPercent(85);
+            finderView.setRectPortraitHeightPercentByWidth(130);
+            finderView.setRectLandscapeWidthPercent(pbw + 2);
+            finderView.setRectLandscapeHeightPercentByWidth(67);
+        } else {
+            finderView.setRectCenterXPercent(50);
+            finderView.setRectCenterYPercent(45);
+            finderView.setRectPortraitWidthPercent(85);
+            finderView.setRectPortraitHeightPercentByWidth(63);
+            finderView.setRectLandscapeWidthPercent(pbw);
+            finderView.setRectLandscapeHeightPercentByWidth(63);
+        }
+        if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+            if (tengineID != TengineID.TIDLPR) {
+                finderView.setRectCenterXPercent(50);
+                finderView.setRectCenterYPercent(50);
+            }
+        }
+        return finderView;
+    }
+
+    /**
+     * 修改提示文本
+     *
+     * @param finderView
+     * @return
+     */
+    protected FinderView setTipText(FinderView finderView) {
+        if (this.tengineID == TengineID.TIDLPR) {
+            finderView.setTipText("将车牌放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDBANK) {
+            finderView.setTipText("将银行卡放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDCARD2) {
+            finderView.setTipText("将身份证放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDXSZCARD) {
+            finderView.setTipText("将行驶证放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDJSZCARD) {
+            finderView.setTipText("将驾驶证放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDTICKET) {
+            finderView.setTipText("将火车票放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDSSCCARD) {
+            finderView.setTipText("将社保卡放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDPASSPORT) {
+            finderView.setTipText("将护照放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDBIZLIC) {
+            finderView.setTipText("将营业执照放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDEEPHK) {
+            finderView.setTipText("将港澳通行证放入框内,并对齐四周边框");
+        } else if (this.tengineID == TengineID.TIDDOCUMENT) {
+            finderView.setTipText("将文档放入框内,并对齐四周边框");
+        }
+
+        return finderView;
+    }
+
+}

+ 113 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/YJFaceActivity.java

@@ -0,0 +1,113 @@
+//package cn.gxeea.zk;
+//
+//import android.Manifest;
+//import android.content.DialogInterface;
+//import android.content.Intent;
+//import android.graphics.Bitmap;
+//import android.graphics.BitmapFactory;
+//import android.os.Bundle;
+//import android.text.TextUtils;
+//import android.view.View;
+//import androidx.annotation.NonNull;
+//import androidx.annotation.Nullable;
+////import com.yunjisoft.ksout.ui.AbsBasePermissionActivity;
+////import com.yunjisoft.ksout.ui.CollectPhotoActivity;
+////import com.yunjisoft.ksout.ui.CollectResultDialog;
+////import com.yunjisoft.ksout.ui.OnEventCallback;
+//
+//import java.util.List;
+//
+//public class YJFaceActivity extends AbsBasePermissionActivity {
+//    @Override
+//    public void initPermissions(@NonNull List<String> permissionArray) {
+//        super.initPermissions(permissionArray);
+//        permissionArray.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+//    }
+//
+//    @Override
+//    public void onAllPermissionsChecked() {
+//
+//    }
+//
+//    @Override
+//    public int getLayoutId() {
+//        setTitle("照片采集");
+//        return R.layout.activity_main;
+//    }
+//
+//    @Override
+//    public void findView() {
+//
+//    }
+//
+//    @Override
+//    public void onBackPressed() {
+//
+//    }
+//
+//    @Override
+//    protected void onCreate(@Nullable Bundle savedInstanceState) {
+//        super.onCreate(savedInstanceState);
+//
+//        start();
+//    }
+//
+//    protected void start() {
+//        startActivityForResult(
+//                new Intent(this, CollectPhotoActivity.class)
+//                        .putExtra(CollectPhotoActivity.SUPPORT_ALBUM, true)
+//                        .putExtra(CollectPhotoActivity.SUPPORT_RECORD, true),
+//                0x333);
+//    }
+//
+//    @Override
+//    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+//        super.onActivityResult(requestCode, resultCode, data);
+//        if (resultCode == RESULT_OK && requestCode == 0x333) {
+//            //源图路径
+//            String srcPath = data == null ? "" : data.getStringExtra("srcPath");
+//            //审核图片名称
+//            String resultName = data == null ? "" : data.getStringExtra("resultName");
+//            //审核图片路径
+//            String resultPath = data == null ? "" : data.getStringExtra("resultPath");
+//
+//            Bitmap bitmap = BitmapFactory.decodeFile(TextUtils.isEmpty(resultPath) ? srcPath : resultPath);
+////            File file = MyFileManager.getCollectPhoto(this, resultName);
+////            Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
+//            showResultDialog(bitmap, data);
+//        } else if (resultCode == RESULT_CANCELED && requestCode == 0x333) {
+//            setResult(RESULT_CANCELED);
+//            finish();
+//        }
+//    }
+//
+//    private void showResultDialog(Bitmap bitmap, Intent data) {
+//        CollectResultDialog dialog = new CollectResultDialog(this);
+//        dialog.setCanceledOnTouchOutside(false);
+//        dialog.setCancelable(true);
+//        dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+//            @Override
+//            public void onCancel(DialogInterface dialog) {
+//                removeFragment("_mCamera");
+//            }
+//        });
+//
+//        dialog.showMyself()
+//                .title("采集结果")
+//                .customLeftButton("重新采集")
+//                .customRightButton("使用照片")
+//                .updateData(true, bitmap, "合格").setCallback(new OnEventCallback() {
+//                    @Override
+//                    public void callback(int key, View view, Bundle bundle) {
+//                        if (key == -1) {
+//                            finish();
+//                        } else if (key == 0) {
+//                            start();
+//                        } else {
+//                            setResult(RESULT_OK, data);
+//                            finish();
+//                        }
+//                    }
+//                });
+//    }
+//}

+ 75 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/YJFaceModule.java

@@ -0,0 +1,75 @@
+package cn.gxeea.zk;
+
+import android.app.Activity;
+import android.content.Intent;
+import com.alibaba.fastjson.JSONObject;
+import com.oumasoft.facelibrary.OumasoftActivity;
+import io.dcloud.feature.uniapp.annotation.UniJSMethod;
+import io.dcloud.feature.uniapp.bridge.UniJSCallback;
+import io.dcloud.feature.uniapp.common.UniModule;
+
+
+public class YJFaceModule extends UniModule {
+
+    static String TAG = "YJFaceModule";
+    public static int REQUEST_CODE = 6000;
+
+    UniJSCallback _callback;
+
+
+//    //run ui thread
+//    @UniJSMethod(uiThread = true)
+//    public void testAsyncFunc(JSONObject options, UniJSCallback callback) {
+//        Log.e(TAG, "testAsyncFunc--" + options);
+//        if (callback != null) {
+//            JSONObject data = new JSONObject();
+//            data.put("code", "success");
+//            callback.invoke(data);
+//            //callback.invokeAndKeepAlive(data);
+//        }
+//    }
+//
+//    //run JS thread
+//    @UniJSMethod(uiThread = false)
+//    public JSONObject testSyncFunc() {
+//        JSONObject data = new JSONObject();
+//        data.put("code", "success");
+//        return data;
+//    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_CODE&&resultCode == -1) {
+            //源图路径
+//            String srcPath = data == null ? "" : data.getStringExtra("srcPath");
+            //审核图片名称
+            String resultName = data == null ? "" : data.getStringExtra("resultName");
+            //审核图片路径
+            String resultPath = data == null ? "" : data.getStringExtra("resultPath");
+
+//            Bitmap bitmap = BitmapFactory.decodeFile(TextUtils.isEmpty(resultPath) ? srcPath : resultPath);
+////            File file = MyFileManager.getCollectPhoto(this, resultName);
+////            Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
+//            if(bitmap!=null)
+//            _callback.invoke(bitmapToBase64(bitmap));
+            _callback.invoke(resultPath);
+        }
+//        else  if (requestCode == REQUEST_CODE && resultCode == 0) {
+//
+//
+//        }
+    }
+
+    @UniJSMethod(uiThread = true)
+    public void YJFace(JSONObject options, UniJSCallback callback) {
+        _callback = callback;
+        if (mUniSDKInstance != null && mUniSDKInstance.getContext() instanceof Activity) {
+            Intent intent = new Intent(mUniSDKInstance.getContext(), OumasoftActivity.class);
+            ((Activity) mUniSDKInstance.getContext()).startActivityForResult(intent, REQUEST_CODE);
+
+        }
+    }
+
+
+}

+ 62 - 0
ses-app/ses-app-android/app/src/main/java/cn/gxeea/zk/wxapi/WXEntryActivity.java

@@ -0,0 +1,62 @@
+
+
+package cn.gxeea.zk.wxapi;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import androidx.annotation.Nullable;
+import com.chinaums.pppay.unify.UnifyPayPlugin;
+import com.tencent.mm.opensdk.constants.ConstantsAPI;
+import com.tencent.mm.opensdk.modelbase.BaseReq;
+import com.tencent.mm.opensdk.modelbase.BaseResp;
+import com.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram;
+import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler;
+import io.dcloud.PandoraEntryActivity;
+
+public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
+
+
+    private String TAG = "WXEntryActivity";
+
+    public WXEntryActivity() {
+        super();
+    }
+
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        startActivity(new Intent(this, PandoraEntryActivity.class));
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onReq(BaseReq baseReq) {
+
+    }
+
+    @Override
+    public void onResp(BaseResp baseResp) {
+        Log.d(TAG, "WEntryActivity onResp");
+        if (baseResp.getType() == ConstantsAPI.COMMAND_LAUNCH_WX_MINIPROGRAM) {
+            WXLaunchMiniProgram.Resp launchMliniProResp = (WXLaunchMiniProgram.Resp) baseResp;
+            String extraData =launchMliniProResp.extMsg;//RID/veG'# ‹button open-type="launchApp"> 4 ) app-parameter ANt
+            UnifyPayPlugin.getInstance (this).getWXListener().onResponse ( this, baseResp);
+        }
+        finish();
+    }
+}
+
+
+

+ 150 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/AutoFocusManager.java

@@ -0,0 +1,150 @@
+package com.oumasoft.facelibrary;
+
+import android.hardware.Camera;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+
+public class AutoFocusManager implements Camera.AutoFocusCallback{
+    private static final String TAG = AutoFocusManager.class.getSimpleName();
+    private static final long AUTO_FOCUS_INTERVAL_MS = 3000L;
+    private static final Collection<String> FOCUS_MODES_CALLING_AF;
+    static {
+        FOCUS_MODES_CALLING_AF = new ArrayList<String>(2);
+        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO);
+        FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO);
+    }
+
+    private boolean stopped;
+    private boolean focusing;
+    private final boolean useAutoFocus;
+    private final Camera camera;
+    private AsyncTask<?,?,?> outstandingTask;
+    private Camera.PreviewCallback mp;
+    public AutoFocusManager(Camera camera, Camera.PreviewCallback mp) {
+        this.camera = camera;
+        String currentFocusMode = camera.getParameters().getFocusMode();
+        useAutoFocus = FOCUS_MODES_CALLING_AF.contains(currentFocusMode);
+        Log.e(TAG, "Current focus mode '" + currentFocusMode + "'; use auto focus? " + useAutoFocus);
+        this.mp = mp;
+        start();
+    }
+
+    @Override
+    public synchronized void onAutoFocus(final boolean success, Camera theCamera) {
+//        if(success){
+//            camera.takePicture(null, null, myjpegCallback);
+//        }
+        if (success) {
+            camera.cancelAutoFocus();
+            setFocusParams(camera);
+        }
+        focusing = false;
+        autoFocusAgainLater();
+    }
+
+    private void setFocusParams(Camera camera) {
+        List<String> focusModeList = camera.getParameters().getSupportedFocusModes();
+        for (String focusMode : focusModeList){//检查支持的对焦
+            if (focusMode.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
+                camera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
+            }else if (focusMode.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)){
+                camera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+            }else if (focusMode.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)){
+                camera.getParameters().setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+            }
+        }
+    }
+
+    private synchronized void autoFocusAgainLater() {
+        if (!stopped && outstandingTask == null) {
+            AutoFocusTask newTask = new AutoFocusTask();
+            try {
+                newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                outstandingTask = newTask;
+            } catch (RejectedExecutionException ree) {
+                Log.e(TAG, "Could not request auto focus", ree);
+            }
+        }
+    }
+
+    /**
+     * 开始自动对焦
+     */
+    public synchronized void start() {
+//        camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
+//            @Override
+//            public void onPreviewFrame(byte[] data, Camera camera)
+//        });
+        if (useAutoFocus) {
+//            outstandingTask = null;
+            if (!stopped && !focusing && camera != null) {
+                try {
+                    camera.setOneShotPreviewCallback(mp);
+                    camera.autoFocus(this);
+                    focusing = true;
+                } catch (RuntimeException re) {
+                    // Have heard RuntimeException reported in Android 4.0.x+; continue?
+                    Log.e(TAG, "Unexpected exception while focusing", re);
+                    // Try again later to keep cycle going
+                    autoFocusAgainLater();
+                }
+                camera.setPreviewCallback(mp);
+            }
+        }
+    }
+
+
+    private synchronized void cancelOutstandingTask() {
+        if (outstandingTask != null) {
+            if (outstandingTask.getStatus() != AsyncTask.Status.FINISHED) {
+                outstandingTask.cancel(true);
+            }
+            outstandingTask = null;
+        }
+    }
+
+    /**
+     * 停止自动对焦
+     */
+    public synchronized void stop() {
+        boolean needCancel = false;
+        if (!stopped) {
+            needCancel = true;
+        }
+        stopped = true;
+        if (useAutoFocus) {
+            cancelOutstandingTask();
+            // Doesn't hurt to call this even if not focusing
+            try {
+                if (needCancel) {
+                    camera.cancelAutoFocus();
+                }
+            } catch (RuntimeException re) {
+                // Have heard RuntimeException reported in Android 4.0.x+; continue?
+                Log.e(TAG, "Unexpected exception while cancelling focusing", re);
+            }
+        }
+
+    }
+
+
+    private final class AutoFocusTask extends AsyncTask<Object,Object,Object> {
+        @Override
+        protected Object doInBackground(Object... voids) {
+            try {
+                Thread.sleep(AUTO_FOCUS_INTERVAL_MS);
+            } catch (InterruptedException e) {
+                // continue
+            }
+            start();
+            return null;
+        }
+    }
+
+}

+ 21 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/BaseApplication.java

@@ -0,0 +1,21 @@
+//package com.oumasoft.facelibrary;
+//
+//import android.app.Application;
+//import android.content.Context;
+//
+//
+//import java.lang.ref.WeakReference;
+//
+//public class BaseApplication extends Application {
+//   private static WeakReference<Context> context;//全局的上下文
+//   @Override
+//   public void onCreate() {
+//      super.onCreate();
+//      context = new WeakReference<Context>(this);
+//
+//   }
+//
+//   public static Context getContext(){
+//      return context.get();
+//   }
+//}

+ 43 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/CameraUtil.java

@@ -0,0 +1,43 @@
+package com.oumasoft.facelibrary;
+
+import android.content.Context;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+
+public class CameraUtil {
+
+    /**
+     * 获取前置摄像头的图像方向
+     * @param context
+     * @return
+     */
+    public static int getFrontCameraDirection(Context context) {
+        return getCameraDirection(context, CameraCharacteristics.LENS_FACING_FRONT);
+    }
+    /**
+     * 获取后置摄像头的图像方向
+     * @param context
+     * @return
+     */
+    public static int getBackCameraDirection(Context context) {
+        return getCameraDirection(context, CameraCharacteristics.LENS_FACING_BACK);
+    }
+ 
+    private static int getCameraDirection(Context context, int cameraFacing) {
+        CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+        try {
+            for (String cameraId : cameraManager.getCameraIdList()) {
+                CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
+                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+                if (facing != null && facing == cameraFacing) {
+                    Integer sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+                    return sensorOrientation != null ? sensorOrientation : 0;
+                }
+            }
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+        return 0;
+    }
+}

+ 161 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/ImageUtil.java

@@ -0,0 +1,161 @@
+package com.oumasoft.facelibrary;
+
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+public class ImageUtil {
+    public static final int YUV420P = 0;
+    public static final int YUV420SP = 1;
+    public static final int NV21 = 2;
+    private static final String TAG = "ImageUtil";
+
+    /***
+     * 此方法内注释以640*480为例
+     * 未考虑CropRect的
+     */
+
+    public static byte[] getBytesFromImageAsType(Image image, int type) {
+        try {
+            //获取源数据,如果是YUV格式的数据planes.length = 3
+            //plane[i]里面的实际数据可能存在byte[].length <= capacity (缓冲区总大小)
+            final Image.Plane[] planes = image.getPlanes();
+
+            //数据有效宽度,一般的,图片width <= rowStride,这也是导致byte[].length <= capacity的原因
+            // 所以我们只取width部分
+            int width = image.getWidth();
+            int height = image.getHeight();
+
+            //此处用来装填最终的YUV数据,需要1.5倍的图片大小,因为Y U V 比例为 4:1:1
+            byte[] yuvBytes = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
+            //目标数组的装填到的位置
+            int dstIndex = 0;
+
+            //临时存储uv数据的
+            byte uBytes[] = new byte[width * height / 4];
+            byte vBytes[] = new byte[width * height / 4];
+            int uIndex = 0;
+            int vIndex = 0;
+
+            int pixelsStride, rowStride;
+            for (int i = 0; i < planes.length; i++) {
+                pixelsStride = planes[i].getPixelStride();
+                rowStride = planes[i].getRowStride();
+
+                ByteBuffer buffer = planes[i].getBuffer();
+
+                //如果pixelsStride==2,一般的Y的buffer长度=640*480,UV的长度=640*480/2-1
+                //源数据的索引,y的数据是byte中连续的,u的数据是v向左移以为生成的,两者都是偶数位为有效数据
+                byte[] bytes = new byte[buffer.capacity()];
+                buffer.get(bytes);
+
+                int srcIndex = 0;
+                if (i == 0) {
+                    //直接取出来所有Y的有效区域,也可以存储成一个临时的bytes,到下一步再copy
+                    for (int j = 0; j < height; j++) {
+                        System.arraycopy(bytes, srcIndex, yuvBytes, dstIndex, width);
+                        srcIndex += rowStride;
+                        dstIndex += width;
+                    }
+                } else if (i == 1) {
+                    //根据pixelsStride取相应的数据
+                    for (int j = 0; j < height / 2; j++) {
+                        for (int k = 0; k < width / 2; k++) {
+                            uBytes[uIndex++] = bytes[srcIndex];
+                            srcIndex += pixelsStride;
+                        }
+                        if (pixelsStride == 2) {
+                            srcIndex += rowStride - width;
+                        } else if (pixelsStride == 1) {
+                            srcIndex += rowStride - width / 2;
+                        }
+                    }
+                } else if (i == 2) {
+                    //根据pixelsStride取相应的数据
+                    for (int j = 0; j < height / 2; j++) {
+                        for (int k = 0; k < width / 2; k++) {
+                            vBytes[vIndex++] = bytes[srcIndex];
+                            srcIndex += pixelsStride;
+                        }
+                        if (pixelsStride == 2) {
+                            srcIndex += rowStride - width;
+                        } else if (pixelsStride == 1) {
+                            srcIndex += rowStride - width / 2;
+                        }
+                    }
+                }
+            }
+
+            //   image.close();
+
+            //根据要求的结果类型进行填充
+            switch (type) {
+                case YUV420P:
+                    System.arraycopy(uBytes, 0, yuvBytes, dstIndex, uBytes.length);
+                    System.arraycopy(vBytes, 0, yuvBytes, dstIndex + uBytes.length, vBytes.length);
+                    break;
+                case YUV420SP:
+                    for (int i = 0; i < vBytes.length; i++) {
+                        yuvBytes[dstIndex++] = uBytes[i];
+                        yuvBytes[dstIndex++] = vBytes[i];
+                    }
+                    break;
+                case NV21:
+                    for (int i = 0; i < vBytes.length; i++) {
+                        yuvBytes[dstIndex++] = vBytes[i];
+                        yuvBytes[dstIndex++] = uBytes[i];
+                    }
+                    break;
+            }
+            return yuvBytes;
+        } catch (final Exception e) {
+            if (image != null) {
+                image.close();
+            }
+            Log.i(TAG, e.toString());
+        }
+        return null;
+    }
+
+    /***
+     * YUV420 转化成 RGB
+     */
+    public static int[] decodeYUV420SP(byte[] yuv420sp, int width, int height)
+    {
+        final int frameSize = width * height;
+        int rgb[] = new int[frameSize];
+        for (int j = 0, yp = 0; j < height; j++) {
+            int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
+            for (int i = 0; i < width; i++, yp++) {
+                int y = (0xff & ((int) yuv420sp[yp])) - 16;
+                if (y < 0)
+                    y = 0;
+                if ((i & 1) == 0) {
+                    v = (0xff & yuv420sp[uvp++]) - 128;
+                    u = (0xff & yuv420sp[uvp++]) - 128;
+                }
+                int y1192 = 1192 * y;
+                int r = (y1192 + 1634 * v);
+                int g = (y1192 - 833 * v - 400 * u);
+                int b = (y1192 + 2066 * u);
+                if (r < 0)
+                    r = 0;
+                else if (r > 262143)
+                    r = 262143;
+                if (g < 0)
+                    g = 0;
+                else if (g > 262143)
+                    g = 262143;
+                if (b < 0)
+                    b = 0;
+                else if (b > 262143)
+                    b = 262143;
+                rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000)
+                        | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
+            }
+        }
+        return rgb;
+    }
+}

+ 36 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/MyFileManager.java

@@ -0,0 +1,36 @@
+package com.oumasoft.facelibrary;
+
+import android.content.Context;
+import android.os.Environment;
+import androidx.annotation.NonNull;
+
+import java.io.File;
+
+public class MyFileManager {
+    public MyFileManager() {
+    }
+
+    public static File getRootDir(@NonNull Context context) {
+        File parent = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+        File root = new File(parent, "oumasoft");
+        if (!root.exists()) {
+            boolean var3 = root.mkdirs();
+        }
+
+        return root;
+    }
+
+    public static File getPhotoCollectDir(@NonNull Context context) {
+        File photoDir = new File(getRootDir(context), "photoCollect");
+        if (!photoDir.exists()) {
+            boolean var2 = photoDir.mkdirs();
+        }
+
+        return photoDir;
+    }
+
+    public static File getCollectPhoto(@NonNull Context context, String name) {
+        File photoDir = getPhotoCollectDir(context);
+        return photoDir.exists() ? new File(photoDir, name) : null;
+    }
+}

+ 42 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/NetUtil.java

@@ -0,0 +1,42 @@
+package com.oumasoft.facelibrary;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+
+
+/**
+ * Description :NetUtil为 网络监控类
+ */
+public class NetUtil {
+    /**
+     * 判断网络情况
+     * @param context  上下文
+     * @return false 表示没有网络 true 表示有网络
+     */
+    public static boolean isNetworkAvailable(Context context) {
+        // 获得网络状态管理器
+        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        if (connectivityManager == null) {
+            return false;
+        } else {
+            // 建立网络数组
+            NetworkInfo[] net_info = connectivityManager.getAllNetworkInfo();
+
+            if (net_info != null) {
+                for (int i = 0; i < net_info.length; i++) {
+                    // 判断获得的网络状态是否是处于连接状态
+                    if (net_info[i].getState() == State.CONNECTED) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+
+
+
+}

+ 25 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/OnClickListener.java

@@ -0,0 +1,25 @@
+package com.oumasoft.facelibrary;
+
+import android.view.View;
+
+/*
+ * Copyright (C) 2010-2017 Alibaba Group Holding Limited.
+ */
+public abstract class OnClickListener implements View.OnClickListener {
+
+    private long lastClickTime = 0;
+    private final int SPACE_TIME = 500;
+
+
+    public boolean isDoubleClick() {
+        long currentTime = System.currentTimeMillis();
+        boolean isDoubleClick;
+        if (currentTime - lastClickTime > SPACE_TIME) {
+            isDoubleClick = false;
+        } else {
+            isDoubleClick = true;
+        }
+        lastClickTime = currentTime;
+        return isDoubleClick;
+    }
+}

+ 655 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/OumasoftActivity.java

@@ -0,0 +1,655 @@
+package com.oumasoft.facelibrary;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import cn.gxeea.zk.R;
+import com.ouma.faceimagelib.FaceQCEngine;
+import com.ouma.faceimagelib.ReturnData;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.json.JSONObject;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OumasoftActivity extends AppCompatActivity {
+
+    private static final int PICK_IMAGE_REQUEST = 1;
+
+    /**
+     * 当前考试的代码。
+     *  1003:自学考试
+     */
+    private static final String ksdm = "1003";
+    ImageView img;
+    TextView tvmsg;
+    String resultName="";
+    String resultPath="";
+    FaceQCEngine face=new FaceQCEngine();   //sdk对象
+
+    private Button btnCamera;
+    private Button btnSave;
+    private Button btnSelect;
+    private TextView tipMsg;
+
+    private View layoutSize;
+    private TextView tvSize;
+
+
+    private String params;
+    private OumasoftActivity mActivity;
+    private boolean hasRequest = false; //是否请求了
+    //    private boolean hasRequest = true; //是否请求了
+//    private boolean canUse = true;     //是否允许使用
+
+
+
+    /**
+     * 唤起系统选择照片页面
+     */
+    private void selectImageFromGallery() {
+        Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+        intent.setType("image/*"); // 仅选择图片
+        startActivityForResult(intent, PICK_IMAGE_REQUEST);
+    }
+    public static Bitmap decodeFileUri(Context context,Uri fileUri) {
+        try {
+            // 使用ContentResolver获取文件的InputStream
+            InputStream inputStream = context.getContentResolver().openInputStream(fileUri);
+            // 使用BitmapFactory.decodeStream来解码InputStream为Bitmap
+            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+            inputStream.close(); // 关闭InputStream
+            return bitmap;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_test);
+
+        requestPermissions();
+
+        mActivity = this;
+        tvmsg=findViewById(R.id.tvMsg);
+        layoutSize=findViewById(R.id.layout_size);
+        layoutSize.setVisibility(View.GONE);    //不展示图像大小
+
+        tvSize=findViewById(R.id.tvSize);
+
+
+        face.ModelInit();   //初始化sdk
+        img=findViewById(R.id.img);
+        img.setImageBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.detect));
+        img.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        // 获取屏幕宽度
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+        int screenWidth = displayMetrics.widthPixels;
+
+        // 设置ImageView的宽度
+        ViewGroup.LayoutParams layoutParams = img.getLayoutParams();
+        layoutParams.width = screenWidth -200;
+        // 如果需要设置高度,可以按照宽度的比例或者直接指定高度
+        layoutParams.height = layoutParams.width*4/3;
+        img.setLayoutParams(layoutParams);
+
+
+        //初始化SharedPreferences工具类
+        SharedPreferencesUtils.getInstance().init(this, "oumasoft");
+
+        tipMsg = findViewById(R.id.tipMsg);
+
+
+        btnCamera = findViewById(R.id.btnCamera);
+        btnSelect = findViewById(R.id.btnCamera2);
+        btnSave = findViewById(R.id.btnSave);
+        View closeButton = findViewById(R.id.capture_close);
+
+        //请求获取sdk参数,sdk需要相应的参数才能使用,且参数是根据考试院要求动态调整,故可以在上一页面请求到传入当前页面,提升用户体验
+        getSDKParams();
+        //判断按钮是否可用
+        judgeCanUse();
+
+        //拍摄照片按钮
+        btnCamera.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (!hasRequest) {  //还没请求到参数,去请求
+                    getSDKParams();
+                    return;
+                }
+
+                Intent intent=new Intent();
+                intent.setClass(OumasoftActivity.this, TakePhoto.class);
+                //String mFilePath= Environment.getExternalStorageDirectory()+ File.separator+nowTime+".jpg";
+                intent.putExtra("pzzppath", "");
+                intent.putExtra("params",params);
+                startActivityForResult(intent, 1001);
+            }
+        });
+        //选择照片按钮
+        btnSelect.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (!hasRequest) {
+                    getSDKParams();
+                    return;
+                }
+
+                selectImageFromGallery();
+            }
+        });
+        //保存照片按钮 将合规照片的路径保存
+        btnSave.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (!hasRequest) {
+                    getSDKParams();
+                    return;
+                }
+
+                //判断当前是否已有合规的照片
+                if(!tvmsg.getText().equals("合规")) {
+                    ToastUtil.show("没有合规的照片");
+                    return;
+                }
+                //保存到SharedPreferences里
+//                SharedPreferencesUtils.getInstance().setValue("resultName", resultName);//照片文件名称
+//                SharedPreferencesUtils.getInstance().setValue("resultPath", resultPath);//照片文件路径
+
+//                finish();
+//                ToastUtil.show("保存成功");
+                Intent intent = new Intent();
+                intent.putExtra("resultName", resultName);
+                intent.putExtra("resultPath", resultPath);
+                setResult(RESULT_OK, intent);
+                finish();
+            }
+        });
+        closeButton.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View view) {
+//                setResult(RESULT_CANCELED);
+//               finish();
+                showDialogFinish();
+            }
+        });
+    }
+
+    /**
+     * 获取sdk的参数
+     */
+    public void getSDKParams() {
+        if (!NetUtil.isNetworkAvailable(mActivity)) {
+            ToastUtil.show("无网络,请连接网络后重试");
+            return;
+        }
+        String url = "https://zphg.gxeea.cn:8808/api/getReviewParms?ksdm=1003";
+        // 创建 OkHttpClient 实例
+        OkHttpClient client = new OkHttpClient();
+        // 构建请求
+        Request request = new Request.Builder()
+                .url(url)
+                .get()
+                .build();
+        new Thread(() -> {
+            // 发送请求并获取响应
+            try (Response response = client.newCall(request).execute()) {
+                if (response.isSuccessful()) {
+                    // 获取响应体
+                    String responseBody = response.body().string();
+                    // 使用 JSONObject 解析外层 JSON
+                    JSONObject jsonObject = new JSONObject(responseBody);
+
+                    int code = jsonObject.getInt("code");
+                    if (code == 200){   //请求成功
+                        hasRequest = true;
+                        // 获取 "data" 对象
+                        JSONObject data = jsonObject.getJSONObject("data");
+                        // 提取 "reviewParms" 字段
+                        String reviewParms = data.getString("reviewParms");
+                        params = reviewParms;
+                        // 输出结果
+                        Log.e("test", "reviewParms: " + reviewParms);
+                        //清空照片合规结果
+                        runOnUiThread(() -> tipMsg.setText(""));
+                    }else {     //请求失败
+                        hasRequest = false;
+                        String msg = jsonObject.getString("msg");
+                        if (TextUtils.isEmpty(msg)) {
+                            msg = "服务器访问异常";
+                        }
+                        final String fMsg = msg;
+                        runOnUiThread(() -> tipMsg.setText(fMsg));
+                    }
+                    //请求到以后,根据请求结果判断按钮是否可用
+                    runOnUiThread(this::judgeCanUse);
+                } else {
+                    Log.e("test", "GET request failed. Code: " + response.code());
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }).start();
+    }
+
+    /**
+     * 根据是否请求到了参数 判断按钮是否可用,更改按钮颜色
+     */
+    private void judgeCanUse() {
+        if (hasRequest) {
+//            btnCamera.setEnabled(true);
+//            btnSelect.setEnabled(true);
+//            btnSave.setEnabled(true);
+            btnCamera.setBackgroundColor(Color.parseColor("#3f76f5"));
+            btnSelect.setBackgroundColor(Color.parseColor("#3f76f5"));
+            btnSave.setBackgroundColor(Color.parseColor("#3f76f5"));
+        }else {
+//            btnCamera.setEnabled(false);
+//            btnSelect.setEnabled(false);
+//            btnSave.setEnabled(false);
+            btnCamera.setBackgroundColor(Color.parseColor("#CCCCCC"));
+            btnSelect.setBackgroundColor(Color.parseColor("#CCCCCC"));
+            btnSave.setBackgroundColor(Color.parseColor("#CCCCCC"));
+        }
+    }
+
+    private static final int PERMISSION_REQUEST_CODE = 1;
+
+    /**
+     * 动态申请权限
+     */
+    private void requestPermissions() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            List<String> permissions = new ArrayList<>();
+
+            // 检查是否缺少权限
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+            }
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+            }
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+                permissions.add(Manifest.permission.CAMERA);
+            }
+
+
+            // 如果有权限未被授予,则申请权限
+            if (!permissions.isEmpty()) {
+                ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), PERMISSION_REQUEST_CODE);
+            }
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (requestCode == PERMISSION_REQUEST_CODE) {
+            boolean allGranted = true;
+
+            for (int result : grantResults) {
+                if (result != PackageManager.PERMISSION_GRANTED) {
+                    allGranted = false;
+                    break;
+                }
+            }
+
+            if (allGranted) {
+                // 所有权限已授予
+                ToastUtil.show("所有权限已授予");
+            } else {
+                // 权限被拒绝
+                ToastUtil.show("权限被拒绝,无法继续操作");
+            }
+        }
+    }
+
+    //get pixels
+    private byte[] getPixelsRGBA(Bitmap image) {
+        // calculate how many bytes our image consists of
+        int bytes = image.getByteCount();
+        ByteBuffer buffer = ByteBuffer.allocate(bytes); // Create a new buffer
+        image.copyPixelsToBuffer(buffer); // Move the byte data to the buffer
+        byte[] temp = buffer.array(); // Get the underlying array containing the
+
+        return temp;
+    }
+
+    /**
+     * 从uri中,获取文件保存路径
+     */
+    private String getRealPathFromURI(Uri contentUri) {
+        String[] projection = {MediaStore.Images.Media.DATA};
+        Cursor cursor = getContentResolver().query(contentUri, projection, null, null, null);
+        if (cursor != null) {
+            int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+            cursor.moveToFirst();
+            String path = cursor.getString(columnIndex);
+            cursor.close();
+            return path;
+        }
+        return null;
+    }
+    public int getBitmapChannelCount(Bitmap bitmap) {
+        Bitmap.Config config = bitmap.getConfig();
+        if (config == Bitmap.Config.ARGB_8888) {
+            return 4; // ARGB -> 4 channels
+        } else if (config == Bitmap.Config.RGB_565) {
+            return 3; // RGB -> 3 channels
+        } else if (config == Bitmap.Config.ALPHA_8) {
+            return 1; // Alpha -> 1 channel
+        } else if (config == Bitmap.Config.ARGB_4444) {
+            return 4; // ARGB -> 4 channels (deprecated)
+        } else {
+            throw new UnsupportedOperationException("Unknown Bitmap.Config: " + config);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null) {
+            //选择照片回来
+            //防止因拍摄照片页面关闭时释放,导致本页面无法使用sdk,可再次初始化一次
+            face.ModelInit();
+            Uri selectedImageUri = data.getData();
+            if (selectedImageUri != null) {
+                //从返回的uri中获取照片文件实际路径
+                String filePath = getRealPathFromURI(selectedImageUri);
+                File f=new File(filePath);
+
+                if (f.exists()) {
+                    //将照片文件转为bitmap
+                    InputStream inputStream = null;
+                    try {
+                        inputStream = getContentResolver().openInputStream(selectedImageUri);
+                    } catch (FileNotFoundException e) {
+                        e.printStackTrace();
+                    }
+                    Bitmap srcBitmap = BitmapFactory.decodeStream(inputStream);
+                    //获取bitmap的通道
+                    int iChannel=getBitmapChannelCount(srcBitmap);
+                    File fname=new File(filePath);
+                    //先记录下选择的照片的名称、路径
+                    resultName=fname.getName();
+                    resultPath=filePath;
+                    //将选择的照片先展示到页面上
+                    img.setImageBitmap(srcBitmap);
+                    tvmsg.setText("正在检测...");
+
+                    //从sdk参数中获取设定的合规照片的输出宽高
+                    int imgWidth = 480, imgHeight = 640;//默认为480*640
+                    String[] splitW = params.split("save_img_width:");
+                    if (splitW.length > 1) {
+                        String[] split = splitW[1].split(";");
+                        try{
+                            imgWidth = Integer.parseInt(split[0]);
+                        }catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    }
+                    String[] splitH = params.split("save_img_height:");
+                    if (splitW.length > 1) {
+                        String[] split = splitH[1].split(";");
+                        try{
+                            imgHeight = Integer.parseInt(split[0]);
+                        }catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    }
+                    //判断选择照片是否已经有水印了
+                    boolean hasSY = false;   //是否有水印
+                    //图片宽高应该和参数设定的输出宽高一致,判断图片宽高小于设定宽高2倍才去检测水印,否则图片过大直接当做没有水印
+                    if (srcBitmap.getWidth() < (imgWidth * 2) && srcBitmap.getHeight() < (imgHeight * 2)) {
+                        //检测是否已经添加数字水印
+                        int result = face.ExtrtSY(resultPath, params); //等于0时,代表有水印
+                        Log.e("test", "检测结果: " + result);
+                        if (result == 0) {
+                            tvmsg.setText("合规");
+                            hasSY = true;
+                        }
+                    }
+                    //没有水印的话,进行添加数字水印
+                    if (!hasSY) {
+                        byte[] imageData = getPixelsRGBA(srcBitmap);
+                        //对照片进行合规化处理
+                        List<ReturnData> returndata = face.FaceQCDetect(imageData, srcBitmap.getWidth(), srcBitmap.getHeight(), iChannel, params, 1);
+                        if (returndata != null) {
+                            tvmsg.setText(returndata.get(0).getInfo()); //是否合规的结果
+                            if ("合规".equals(returndata.get(0).getInfo())) {
+                                //合规了
+                                try {
+                                    Bitmap bitmap = returndata.get(0).getBitmap();
+                                    int channel = getBitmapChannelCount(bitmap);
+                                    byte[] imgData = getPixelsRGBA(bitmap);
+
+                                    //获取文件保存路径
+                                    File folder = MyFileManager.getPhotoCollectDir(OumasoftActivity.this);
+                                    String name = String.valueOf(System.currentTimeMillis());
+                                    String fileName = name + ".jpg";
+                                    //获取文件保存名称
+                                    String fpName = getFilePathName(folder.getPath(), fileName);
+
+                                    //将已合规处理、还未加水印时的照片展示到界面上
+                                    img.setImageBitmap(bitmap);
+
+                                    //添加水印,sdk返回的是字节数组,直接将其写到文件中。字节数组转为bitmap的话会丢失水印
+                                    byte[] bytes = face.EmbedSY(imgData, bitmap.getWidth(), bitmap.getHeight(), channel, params);
+                                    if (bytes != null) {
+                                        SaveJpg(bytes, fileName, fpName);   //保存到文件
+                                        //将数据转为bitmap,展示到界面上
+                                        Bitmap bitmapE = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+                                        img.setImageBitmap(bitmapE);
+                                    } else {
+                                        runOnUiThread(() -> ToastUtil.show("照片无法保存,请重试"));
+                                    }
+                                    //记录下最终合规处理后、加完水印的照片的名称和路径
+                                    resultName = fileName;
+                                    resultPath = fpName;
+
+                                    //获取最终照片文件大小,并展示到界面上
+                                    img.postDelayed(new Runnable() {
+                                        @Override
+                                        public void run() {
+//                                            File file = new File(savePathName);
+                                            File file = new File(fpName);
+                                            double size = file.length() / 1024.0;
+                                            layoutSize.setVisibility(View.VISIBLE);
+                                            tvSize.setText(String.format("%.2f", size) + "KB");
+                                        }
+                                    }, 300);
+
+                                } catch (Exception e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                        }
+                    }
+
+                } else {
+                    ToastUtil.show("无法获取图片路径");
+                }
+            }
+        }
+        if(requestCode == 1001&&data!=null){//拍照回来
+            //回来判断是否有了照片 拍照后会直接合规处理并加水印,将照片保存为文件,返回来文件路径
+            resultName=data.getStringExtra("resultName");
+            resultPath=data.getStringExtra("resultPath");
+            String mFilePath= resultPath;
+            File fexist=new File(mFilePath);
+            //如果文件不存在,return
+            if(!fexist.exists()) {
+                return;
+            }
+            //将文件转为bitmap
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            Bitmap bmp=BitmapFactory.decodeFile(mFilePath,options);
+            //将图片展示到界面上
+            img.setImageBitmap(bmp);
+            //拍照回来,只要返回了照片路径,就代表是已经合规处理并加水印完成了的
+            tvmsg.setText("合规");
+
+            //计算文件大小并展示
+            double size = fexist.length() / 1024.0;
+            layoutSize.setVisibility(View.VISIBLE);
+            tvSize.setText(String.format("%.2f", size) + "KB");
+        }
+    }
+
+    /**
+     * 将bitmap保存到指定路径下
+     * 若Android系统版本在Q及以上,会使用分区存储模式,获取相应的系统保存路径并返回
+     * @param bitmap    bitmap对象
+     * @param fileName  文件名称
+     * @param filePathName  文件保存路径 (Android Q以下时使用)
+     * @return  文件保存路径(Android Q及以上时 会和传入的路径不一致)
+     */
+    public String SaveJpg(Bitmap bitmap ,String fileName,String filePathName) {
+        try {
+            File file = new File(filePathName);
+            OutputStream out = null;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                // 分区存储模式
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+                values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
+
+                Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+                if (uri != null) {
+                    out = getContentResolver().openOutputStream(uri);
+                    filePathName = getRealPathFromURI(uri);
+                }
+            } else {
+                out = new FileOutputStream(file);
+            }
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);   //保存质量为80%
+            out.flush();
+            out.close();
+            return filePathName;
+        } catch (FileNotFoundException fileNotFoundException) {
+            fileNotFoundException.printStackTrace();
+        } catch (IOException ioException) {
+            ioException.printStackTrace();
+        }
+        return "";
+    }
+    /**
+     * 将图片的字节数组数据保存到指定路径下
+     * 若Android系统版本在Q及以上,会使用分区存储模式,获取相应的系统保存路径并返回
+     * @param bytes    图片的字节数组数据
+     * @param fileName  文件名称
+     * @param filePathName  文件保存路径 (Android Q以下时使用)
+     * @return  文件保存路径(Android Q及以上时 会和传入的路径不一致)
+     */
+    public String SaveJpg(byte[] bytes,String fileName,String filePathName) {
+        try {
+            File file = new File(filePathName);
+            OutputStream out = null;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                // 分区存储模式
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+                values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
+
+                Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+                if (uri != null) {
+                    out = getContentResolver().openOutputStream(uri);
+                    filePathName = getRealPathFromURI(uri);
+                }
+            } else {
+                out = new FileOutputStream(file);
+            }
+            out.write(bytes);
+            out.flush();
+            out.close();
+            return filePathName;
+        } catch (FileNotFoundException fileNotFoundException) {
+            fileNotFoundException.printStackTrace();
+        } catch (IOException ioException) {
+            ioException.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * 根据文件夹路径、文件名称,返回文件保存路径(文件夹路径+名称)
+     * 当系统版本为Android Q及以上时,将获取系统保存路径并返回
+     */
+    public String getFilePathName(String filePath, String fileName) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            // 分区存储模式
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+            values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
+
+            Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            if (uri != null) {
+                return getRealPathFromURI(uri);
+            }
+        }
+        File file = new File(filePath, fileName);
+        return file.getPath();
+    }
+
+    @Override
+    public void onBackPressed() {
+        showDialogFinish();
+    }
+    //弹出框的提示
+    public void showDialogFinish(){
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setMessage("返回后已经采集的合规照片将不能保存。是否确认退出照片采集吗?");
+        builder.setTitle("温馨提示");
+        builder.setCancelable(true);
+        builder.setNegativeButton("取消", (dialog, which) -> dialog.dismiss());
+        builder.setPositiveButton("确定",
+                (dialog, which) -> {
+                    dialog.dismiss();
+                    OumasoftActivity.super.onBackPressed();
+                    finish();
+                    //System.exit(0);
+                });
+        if(!isFinishing()){
+            builder.create().show();
+        }
+    }
+}

+ 98 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/SharedPreferencesUtils.java

@@ -0,0 +1,98 @@
+package com.oumasoft.facelibrary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import androidx.annotation.NonNull;
+
+/**
+ * Created by dandy on 2016/5/19.
+ */
+
+public class SharedPreferencesUtils {
+
+    private static SharedPreferencesUtils instance;
+
+    private static final String DEFAULT_NAME = "dandy.xml";
+
+    private String name = DEFAULT_NAME;
+
+    private Context mContext;
+
+    public static SharedPreferencesUtils getInstance(){
+        if(instance == null){
+            synchronized (SharedPreferencesUtils.class){
+                if(instance == null){
+                    instance = new SharedPreferencesUtils();
+                }
+            }
+        }
+        return instance;
+    }
+
+    /**
+     * 初始化上下文参数以及文件名
+     * @param context,上下文
+     * @param name,文件名
+     */
+    public void init(Context context,String name){
+        this.mContext = context.getApplicationContext();
+        this.name = name;
+    }
+
+    /**
+     * 保存数据,泛型方法
+     * @param key,键值
+     * @param value,数据
+     * @param <V>
+     */
+    public <V> void setValue(@NonNull String key,V value){
+        SharedPreferences sp = mContext.getSharedPreferences(name,Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sp.edit();
+        if(value instanceof String){
+            editor.putString(key,(String)value);
+        }else if(value instanceof Integer){
+            editor.putInt(key,(Integer)value);
+        }else if(value instanceof Long){
+            editor.putLong(key,(Long)value);
+        }else if(value instanceof Boolean){
+            editor.putBoolean(key,(Boolean)value);
+        }else if(value instanceof Float){
+            editor.putFloat(key,(Float)value);
+        }
+        editor.commit();
+    }
+
+    /**
+     * 读取数据,泛型方法
+     * @param key,键值
+     * @param defaultValue,默认值
+     * @param <V>
+     * @return
+     */
+
+    public <V> V getValue(@NonNull String key, V defaultValue){
+        SharedPreferences sp = mContext.getSharedPreferences(name,Context.MODE_PRIVATE);
+        Object value = defaultValue;
+        if(defaultValue instanceof String){
+            value = sp.getString(key,(String)defaultValue);
+        }else if(defaultValue instanceof Integer){
+            value = sp.getInt(key,(Integer) defaultValue);
+        }else if(defaultValue instanceof Long){
+            value = sp.getLong(key,(Long) defaultValue);
+        }else if(defaultValue instanceof Boolean){
+            value = sp.getBoolean(key, (Boolean) defaultValue);
+        }else if(defaultValue instanceof Float){
+            value = sp.getFloat(key, (Float) defaultValue);
+        }
+        return (V)value;
+    }
+
+    /**
+     * 清除数据
+     */
+    public void clearData(){
+        SharedPreferences.Editor editor = mContext.getSharedPreferences(name,Context.MODE_PRIVATE).edit();
+        editor.clear();
+        editor.commit();
+    }
+}

+ 862 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/TakePhoto.java

@@ -0,0 +1,862 @@
+package com.oumasoft.facelibrary;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.*;
+import android.hardware.camera2.*;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.net.Uri;
+import android.os.*;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+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;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import cn.gxeea.zk.R;
+import com.ouma.faceimagelib.FaceQCEngine;
+import com.ouma.faceimagelib.ReturnData;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class TakePhoto extends AppCompatActivity {
+
+    private static final int CAMERA_PERMISSION_REQUEST_CODE = 100;
+    private TextureView textureView;
+    private CameraDevice cameraDevice;
+    private CameraCaptureSession captureSession;
+    private Size previewSize;
+    private String cameraId;
+    private int currentCameraIndex = 1; // 0 for back camera, 1 for front camera
+    private CameraManager cameraManager;
+    private String[] cameraIdList;
+    private AutoFocusManager autoFocusManager;
+    private ImageView btn_changeFront;
+
+    private int mWidth=0,mHeight=0,screenWidth=0;
+    ImageView btnOK,btnClose,imgtest;
+    private ImageReader imageReader;
+//    CameraCaptureSession.CaptureCallback previewCallback;//预览回调
+    boolean bPZClick=false;
+    FaceQCEngine face;//sdk对象
+    String showmsg="";
+    TextView tvFBL;
+    private final Size DESIRED_PREVIEW_SIZE = new Size(1920, 1080);
+
+
+    // 处理屏幕旋转角度
+    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+    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 LinearLayout layoutButton;
+    private ImageView ivGreen;
+    private ImageView ivRed;
+    private TextView tvResult;
+
+    private String params = "";
+
+
+    //get pixels
+    private byte[] getPixelsRGBA(Bitmap image) {
+        // calculate how many bytes our image consists of
+        int bytes = image.getByteCount();
+        ByteBuffer buffer = ByteBuffer.allocate(bytes); // Create a new buffer
+        image.copyPixelsToBuffer(buffer); // Move the byte data to the buffer
+        byte[] temp = buffer.array(); // Get the underlying array containing the
+
+        return temp;
+    }
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (requestCode == PERMISSION_REQUEST_CODE) {
+            boolean allGranted = true;
+
+            for (int result : grantResults) {
+                if (result != PackageManager.PERMISSION_GRANTED) {
+                    allGranted = false;
+                    break;
+                }
+            }
+
+            if (allGranted) {
+                // 所有权限已授予
+                ToastUtil.show("所有权限已授予");
+            } else {
+                // 权限被拒绝
+                ToastUtil.show("权限被拒绝,无法继续操作");
+            }
+        }
+    }
+
+    private static final int PERMISSION_REQUEST_CODE = 1;
+
+    private void requestPermissions() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            List<String> permissions = new ArrayList<>();
+
+            // 检查是否缺少权限
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+            }
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+            }
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+                permissions.add(Manifest.permission.CAMERA);
+            }
+
+
+            // 如果有权限未被授予,则申请权限
+            if (!permissions.isEmpty()) {
+                ActivityCompat.requestPermissions(this, permissions.toArray(new String[0]), PERMISSION_REQUEST_CODE);
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.takephoto);
+        requestPermissions();
+
+        String paramsLast = getIntent().getStringExtra("params");
+        if (!TextUtils.isEmpty(paramsLast)){
+            params = paramsLast;
+        }else {
+            ToastUtil.show("参数丢失,请重试");
+            finish();
+        }
+
+        tvFBL=findViewById(R.id.tvFBL);
+        //tvFBL.setVisibility(View.GONE);
+        imgtest=findViewById(R.id.imgtest);
+        imgtest.setVisibility(View.GONE);
+
+        ivGreen = findViewById(R.id.iv_green);  //绿色脸部轮廓
+        ivRed = findViewById(R.id.iv_red);      //红色轮廓
+        tvResult = findViewById(R.id.tv_result);      //人脸合规识别结果
+
+        layoutButton = findViewById(R.id.layout_button);
+        btnOK=(ImageView)findViewById(R.id.btnOK);  //拍摄按钮
+        btnClose=(ImageView)findViewById(R.id.btnClose);
+        textureView = findViewById(R.id.textureView);
+        textureView.setSurfaceTextureListener(textureListener);
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+        screenWidth = displayMetrics.widthPixels;
+
+        // 初始化 CameraManager
+        cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
+        try {
+            cameraIdList = cameraManager.getCameraIdList();
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+
+        //是否合规时自动拍照
+        boolean autoPhoto = SharedPreferencesUtils.getInstance().getValue("autoPhoto", false);
+        if (autoPhoto) {
+            btnOK.setVisibility(View.GONE);
+        }
+
+        btn_changeFront = findViewById(R.id.btn_changeFront);
+        btn_changeFront.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (isDoubleClick())
+                    return;
+                switchCamera();
+            }
+        });
+        // 关闭
+        btnClose.setOnClickListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+
+                //if(mBitmap!=null)
+                    //mBitmap.recycle();
+
+                //Intent it=new Intent();
+                //setResult(RESULT_OK,it);
+                finish();
+            }
+        });
+
+        // 拍摄按钮
+        btnOK.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                takePhoto();
+            }
+        });
+    }
+
+    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
+        @Override
+        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
+            mWidth=width;
+            mHeight=height;
+            openCamera(width, height);
+        }
+        @Override
+        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
+            configureTransform(width, height);
+        }
+        @Override
+        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
+            return false;
+        }
+        @Override
+        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+        }
+    };
+    CameraCharacteristics characteristics;
+    private boolean isCameraOpened = false;
+    private void openCamera(int width, int height) {
+        if (isCameraOpened) {
+            Log.d("CameraDebug", "Camera is already opened");
+            return;
+        }
+        //创建sdk对象并初始化
+        if(face==null) {
+            face=new FaceQCEngine();
+            face.ModelInit();
+        }
+        isCameraOpened = true;
+        CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
+        try {
+            cameraId = manager.getCameraIdList()[currentCameraIndex];
+             characteristics = manager.getCameraCharacteristics(cameraId);
+            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+            if (map != null) {
+                // 获取合适的预览尺寸
+                previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
+                previewSize = new Size(DESIRED_PREVIEW_SIZE.getWidth(), DESIRED_PREVIEW_SIZE.getHeight());
+//                imageReader = ImageReader.newInstance(width, height,
+//                        ImageFormat.YUV_420_888, 2); // 设置最大图像缓冲区数量
+                imageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(),
+                        ImageFormat.YUV_420_888, 2); // 设置最大图像缓冲区数量
+                tvFBL.setText("预览分辨率:"+previewSize.getWidth()+"*"+previewSize.getHeight());
+                Log.e("test", "预览分辨率:"+previewSize.getWidth()+"*"+previewSize.getHeight());
+                imageReader.setOnImageAvailableListener(this::onImageAvailable, null);
+
+                configureTransform(width, height);
+            }
+
+            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
+                return;
+            }
+            manager.openCamera(cameraId, stateCallback, null);
+
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    //旋转bitmap
+    private Bitmap rotateBitmap(Bitmap bitmap, int degrees) {
+        Matrix matrix = new Matrix();
+        matrix.postRotate(degrees);
+        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+    }
+
+    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
+        @Override
+        public void onOpened(@NonNull CameraDevice camera) {
+            cameraDevice = camera;
+            createCameraPreview();
+            configureTransform(textureView.getWidth(), textureView.getHeight());
+        }
+        @Override
+        public void onDisconnected(@NonNull CameraDevice camera) {
+            cameraDevice.close();
+        }
+        @Override
+        public void onError(@NonNull CameraDevice camera, int error) {
+            cameraDevice.close();
+            cameraDevice = null;
+        }
+    };
+
+    /**
+     * 创建相机预览
+     */
+    private void createCameraPreview() {
+        try {
+            SurfaceTexture texture = textureView.getSurfaceTexture();
+            assert texture != null;
+
+            texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+            Surface surface = new Surface(texture);
+            Log.e("test", "surface   分辨率:"+previewSize.getWidth()+"*"+previewSize.getHeight());
+
+            CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            builder.addTarget(surface);
+            // 设置自动对焦
+            builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
+            //CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            builder.addTarget(imageReader.getSurface());
+
+            // 设置对焦区域,自动对焦会在这个区域进行
+            Rect focusArea = new Rect(100, 100, 500, 500); // 可以修改这个区域大小
+            builder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{new MeteringRectangle(focusArea, MeteringRectangle.METERING_WEIGHT_MAX)});
+            builder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{new MeteringRectangle(focusArea, MeteringRectangle.METERING_WEIGHT_MAX)});
+            int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+            // 获取设备的旋转角度
+            int rotation = getWindowManager().getDefaultDisplay().getRotation();
+            // 计算拍照的方向
+            int jpegOrientation = (270+sensorOrientation + ORIENTATIONS.get(rotation)) % 360;
+
+            // 设置 JPEG 的方向
+            builder.set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation);
+            cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), new CameraCaptureSession.StateCallback() {
+                @Override
+                public void onConfigured(@NonNull CameraCaptureSession session) {
+                    captureSession = session;
+                    try {
+                        captureSession.setRepeatingRequest(builder.build(), null, null);
+                    } catch (CameraAccessException e) {
+                        e.printStackTrace();
+                    }
+                }
+                @Override
+                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
+                    ToastUtil.show("Camera configuration failed");
+                }
+            }, null);
+
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    //获取预览画面大小
+    private Size chooseOptimalSize(Size[] choices, int width, int height) {
+        final double ASPECT_TOLERANCE = 0.1;
+        double targetRatio = (double) height / width;
+
+        if (choices == null) return null;
+
+        Size optimalSize = null;
+        double minDiff = Double.MAX_VALUE;
+
+        int targetHeight = height;
+
+        for (Size size : choices) {
+            double ratio = (double) size.getWidth() / size.getHeight();
+            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
+            if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+                optimalSize = size;
+                minDiff = Math.abs(size.getHeight() - targetHeight);
+            }
+        }
+
+        if (optimalSize == null) {
+            minDiff = Double.MAX_VALUE;
+            for (Size size : choices) {
+                if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+                    optimalSize = size;
+                    minDiff = Math.abs(size.getHeight() - targetHeight);
+                }
+            }
+        }
+        return optimalSize;
+    }
+
+    /**
+     * 设置输出画面缩放适配
+     *
+     * @param viewWidth  mTextureView 的宽度
+     * @param viewHeight mTextureView 的高度
+     */
+    private void configureTransform(int viewWidth, int viewHeight) {
+
+        //屏幕方向
+        //int rotation = windowManager.getDefaultDisplay().getRotation();
+        final Matrix matrix = new Matrix();
+        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 (previewSize.getWidth() < viewHeight) {
+
+            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
+            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+            float scale = Math.max((float) viewHeight / previewSize.getWidth(), (float) viewWidth / previewSize.getHeight());
+            //设置缩放
+            matrix.postScale(scale, scale, centerX, centerY);
+            //设置旋转角度
+            //matrix.postRotate(90 * (rotation - 2), centerX, centerY);
+        } else {
+
+        }
+        textureView.post(new Runnable() {
+            @Override
+            public void run() {
+                textureView.setTransform(matrix);
+            }
+        });
+    }
+
+    private void closeCamera() {
+        if (cameraDevice != null) {
+            cameraDevice.close();
+            cameraDevice = null;
+        }
+        isCameraOpened=false;
+    }
+    private void switchCamera() {
+        if(currentCameraIndex==0) {
+            currentCameraIndex = 1;
+        }else {
+            currentCameraIndex = 0;
+        }
+        //currentCameraIndex = (currentCameraIndex + 1) % cameraIdList.length;
+        closeCamera();
+
+        //openCamera(textureView.getWidth(), textureView.getHeight());
+        openCamera(DESIRED_PREVIEW_SIZE.getWidth(), DESIRED_PREVIEW_SIZE.getHeight());
+
+    }
+    // 拍照方法
+    private void takePhoto() {
+        if(showmsg.equals("请保持当前位置拍照") || showmsg.equals("请保持当前位置")) {
+            bPZClick = true;    //可以进行拍照
+        }else {
+            runOnUiThread(() -> ToastUtil.show("请调整好位置再进行拍照"));
+        }
+    }
+
+    /**
+     * 释放相机资源
+     */
+    private void releaseCameraResources() {
+        try {
+            if (captureSession != null) {
+                captureSession.stopRepeating();
+                captureSession.abortCaptures();
+                captureSession.close();
+                captureSession = null;
+                if (cameraDevice != null) {
+                    cameraDevice.close();
+                    cameraDevice = null;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    int ncount=0;
+    Image image=null;
+    boolean bCanDetect=true;    //是否可以进行照片处理
+    boolean bFinish =true;  //true时代表可以处理照片,false时代表已经拍摄照片了,不用再处理了
+    private ExecutorService imageProcessingExecutor = Executors.newSingleThreadExecutor();
+    Bitmap rotatedBitmap=null;
+    long currentTime = 0, imgTime = 0;
+
+    // 预览回调, 可获取预览图像
+    private void onImageAvailable(ImageReader reader) {
+
+        //图像获取间隔200毫秒,防止太快 处理速度跟不上
+        if (System.currentTimeMillis() - currentTime < 200) {
+            return;
+        }
+        currentTime = System.currentTimeMillis();
+        Log.e("test","次数:"+ncount++);
+
+        try {
+            //已获取图像为空时,去获取最新的图像;不为空时,且2秒都没获取过新图像,就将其释放掉
+            if (image == null) {
+                imgTime = System.currentTimeMillis();
+                // 获取最新图像
+                image = reader.acquireLatestImage();
+                //使用线程池 处理图像
+                imageProcessingExecutor.submit(() -> processImage());
+            }else {
+                if (System.currentTimeMillis() - imgTime > 2000) {
+                    if (image != null) {
+                        image.close(); // 确保释放图像资源
+                        image=null;
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            runOnUiThread(() -> ToastUtil.show("异常 " + e.getMessage()));
+        }
+
+    }
+    String fileName="",filePathName="";
+    int rotateWidth, rotateHeight, screenW, screenH, faceBottom, faceWidth, faceHeight, faceX, faceY;
+    private void processImage() {
+        // 在后台线程处理图像
+        try {
+            if(bFinish)
+                if (image != null&& (bCanDetect||bPZClick)) {
+                    // bCanDetect:控制每次只处理一个图像;或者点了拍照了,也继续进行处理
+                    bCanDetect = false;
+                    if (bPZClick) { //点了拍照以后,后续的都不再处理
+                        bFinish = false;
+                    }
+
+                    int imageWidth = image.getWidth();
+                    int imageHeight = image.getHeight();
+
+
+                    byte[] data68 = ImageUtil.getBytesFromImageAsType(image, 2);
+                    int rgb[] = ImageUtil.decodeYUV420SP(data68, imageWidth, imageHeight);
+                    Bitmap bitmap = Bitmap.createBitmap(rgb, 0, imageWidth,
+                            imageWidth, imageHeight,
+                            Bitmap.Config.ARGB_8888);
+
+
+                    if (image != null) {
+                        image.close(); // 确保释放图像资源
+                        image = null;
+                    }
+
+                    // 旋转Bitmap,确保显示方向正确
+                    int degrees = 0;
+                    if (currentCameraIndex == 0) {//后置摄像头
+                        degrees = CameraUtil.getBackCameraDirection(getApplicationContext());
+                    }else { //前置
+                        degrees = CameraUtil.getFrontCameraDirection(getApplicationContext());
+                    }
+                    rotatedBitmap = rotateBitmap(bitmap, degrees);
+                    if (bitmap != null) {
+                        bitmap.recycle();
+                    }
+
+                }
+
+            byte[] imageData = getPixelsRGBA(rotatedBitmap);
+            //tvmsg.setText("正在检测...");
+            List<ReturnData> returndata=null;
+
+            if(face != null) {
+                if(!bPZClick) { //不是拍照,只对图片检测是否合规
+                    returndata = face.FaceQCDetect(imageData, rotatedBitmap.getWidth(), rotatedBitmap.getHeight(), 4, params, 0);
+                }else { //点了拍照后,对图片进行合规处理
+                    returndata = face.FaceQCDetect(imageData, rotatedBitmap.getWidth(), rotatedBitmap.getHeight(), 4, params, 1);
+                }
+            }
+            //合规结果
+            if(returndata!=null) {
+                //returndata.get(0).getInfo() 获取合规结果描述
+                // 合规时,FaceQCDetect传入0时,只检测是否合规,返回结果为"请保持当前位置拍照";
+                // 传入1时,进行合规处理,返回结果为"合规"
+                showmsg=returndata.get(0).getInfo();
+               // runOnUiThread(() -> Toast.makeText(this, "检测结果: "+showmsg, Toast.LENGTH_SHORT).show());
+                Log.e("camera",showmsg);
+
+                if(showmsg.startsWith("请保持当前位置") || showmsg.equals("合规")) {
+                    updateSurface();
+                }else {
+                    updateSurface();
+                    bCanDetect=true;   //不合规,设置为可继续进行处理
+                    return;
+                }
+                Bitmap bitmapnew=returndata.get(0).getBitmap(); //合规处理后的照片
+                if(bPZClick&&bitmapnew!=null) { //点了拍照,并且有合规处理完成的照片,即已经拍完了
+                    bPZClick=false;
+                    releaseCameraResources();   //释放相机资源
+                    if (imageReader != null) {
+                        imageReader.close();    //释放图像捕捉类
+                        imageReader = null;
+                    }
+
+
+                    String name = String.valueOf(System.currentTimeMillis());
+                    String tempfileName = name + ".jpg";
+                    try{
+                        fileName = tempfileName;
+                        String filePath = Environment.getExternalStorageDirectory()+"/";
+                        filePathName = getFilePathName(filePath, fileName);
+                        byte[] imgData = getPixelsRGBA(bitmapnew);
+
+
+                        //给合规图片加水印
+                        byte[] bytes = face.EmbedSY(imgData, bitmapnew.getWidth(), bitmapnew.getHeight(), 4, params);
+
+                        //将返回的字节数组数据 保存
+                        if (bytes != null) {
+                            SaveJpg(bytes,fileName,filePathName);
+                        }else {
+                            runOnUiThread(() -> ToastUtil.show("照片无法保存,请重试"));
+                        }
+
+                        //保存拍摄的原图
+                        String name2 = String.valueOf(System.currentTimeMillis());
+                        String fileName2 = name2 + ".jpg";
+                        String filePathName2 = Environment.getExternalStorageDirectory()+"/"+fileName2;
+                        SaveJpg(rotatedBitmap,fileName2,filePathName2); //拍照原图
+                        //bitmap.recycle();
+
+                        //将合规、加水印后的照片传回上一页面
+                        Intent it=new Intent();
+                        it.putExtra("resultName", fileName);
+                        it.putExtra("resultPath", filePathName);
+                        setResult(1001,it);
+                        //关闭页面
+                        handler.sendEmptyMessage(0);
+                        //resultName=fileName;
+                        //resultPath=filePathName;
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        runOnUiThread(() -> ToastUtil.show("异常: " + e.getMessage()));
+                    }
+                }
+            }
+            //bitmap.recycle();
+            bCanDetect=true;    //一张图片的处理完,允许下一个照片进行处理
+        } catch (Exception e) {
+            e.printStackTrace();
+            runOnUiThread(() -> ToastUtil.show("异常: " + e.getMessage()));
+        } finally {
+            if (image != null) {
+                image.close(); // 确保释放图像资源
+                image=null;
+            }
+        }
+    }
+
+    /**
+     * 根据文件夹路径、文件名称,返回文件保存路径(文件夹路径+名称)
+     * 当系统版本为Android Q及以上时,将获取系统保存路径并返回
+     */
+    public String getFilePathName(String filePath, String fileName) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            // 分区存储模式
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+            values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
+
+            Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            if (uri != null) {
+                return getRealPathFromURI(uri);
+            }
+        }
+        File file = new File(filePath, fileName);
+        return file.getPath();
+    }
+
+    /**
+     * 将bitmap保存到指定路径下
+     * 若Android系统版本在Q及以上,会使用分区存储模式,获取相应的系统保存路径并返回
+     * @param bitmap    bitmap对象
+     * @param fileName  文件名称
+     * @param filePathName  文件保存路径 (Android Q以下时使用)
+     * @return  文件保存路径(Android Q及以上时 会和传入的路径不一致)
+     */
+    public String SaveJpg(Bitmap bitmap ,String fileName,String filePathName) {
+        try {
+            File file = new File(filePathName);
+            OutputStream out = null;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                // 分区存储模式
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+                values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
+
+                Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+                if (uri != null) {
+                    out = getContentResolver().openOutputStream(uri);
+                    filePathName = getRealPathFromURI(uri);
+                }
+            } else {
+                out = new FileOutputStream(file);
+            }
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
+            out.flush();
+            out.close();
+            return filePathName;
+        } catch (FileNotFoundException fileNotFoundException) {
+            fileNotFoundException.printStackTrace();
+        } catch (IOException ioException) {
+            ioException.printStackTrace();
+        }
+        return "";
+    }
+    /**
+     * 将图片的字节数组数据保存到指定路径下
+     * 若Android系统版本在Q及以上,会使用分区存储模式,获取相应的系统保存路径并返回
+     * @param bytes    图片的字节数组数据
+     * @param fileName  文件名称
+     * @param filePathName  文件保存路径 (Android Q以下时使用)
+     * @return  文件保存路径(Android Q及以上时 会和传入的路径不一致)
+     */
+    public String SaveJpg(byte[] bytes,String fileName,String filePathName) {
+        try {
+            File file = new File(filePathName);
+            OutputStream out = null;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                // 分区存储模式
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
+                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
+                values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
+
+                Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+                if (uri != null) {
+                    out = getContentResolver().openOutputStream(uri);
+                    filePathName = getRealPathFromURI(uri);
+                }
+            } else {
+                out = new FileOutputStream(file);
+            }
+            out.write(bytes);
+            out.flush();
+            out.close();
+            return filePathName;
+        } catch (FileNotFoundException fileNotFoundException) {
+            fileNotFoundException.printStackTrace();
+        } catch (IOException ioException) {
+            ioException.printStackTrace();
+        }
+        return "";
+    }
+    /**
+     * 从uri中,获取文件保存路径
+     */
+    private String getRealPathFromURI(Uri contentUri) {
+        String[] projection = {MediaStore.Images.Media.DATA};
+        Cursor cursor = getContentResolver().query(contentUri, projection, null, null, null);
+        if (cursor != null) {
+            int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+            cursor.moveToFirst();
+            String path = cursor.getString(columnIndex);
+            cursor.close();
+            return path;
+        }
+        return null;
+    }
+
+    /**
+     * 初始化并获取预览回调对象
+     * @return
+     */
+//    private CameraCaptureSession.CaptureCallback getPreviewCallback (){
+//        if(previewCallback == null){
+//            previewCallback = new CameraCaptureSession.CaptureCallback(){
+//                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
+//                }
+//            };
+//        }
+//        return previewCallback;
+//    }
+
+    final Handler handler=new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == 0) {
+                Log.e("camera2","收到关闭消息");
+                btnClose.performClick();
+            }
+        }
+    };
+
+
+    public static int dp2px(Context context, float dipValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return Math.round(dipValue * scale);
+    }
+
+    /**
+     * 更新界面
+     */
+    private void updateSurface() {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (!TextUtils.isEmpty(showmsg)) {
+                    if (showmsg.startsWith("请保持当前位置")|| showmsg.equals("合规")) {
+                        //绿色脸部轮廓
+                        ivGreen.setVisibility(View.VISIBLE);
+                        ivRed.setVisibility(View.INVISIBLE);
+                    } else {
+                        //红色脸部轮廓
+                        ivRed.setVisibility(View.VISIBLE);
+                        ivGreen.setVisibility(View.INVISIBLE);
+                    }
+                    tvResult.setText(showmsg);
+                    tvResult.setVisibility(View.VISIBLE);
+                }else {
+                    tvResult.setVisibility(View.INVISIBLE);
+                }
+            }
+        });
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        openCamera(DESIRED_PREVIEW_SIZE.getWidth(), DESIRED_PREVIEW_SIZE.getHeight());
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        closeCamera();
+        releaseCameraResources();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        //face.ModelUnInit();
+        releaseCameraResources();
+        if(face!=null) {
+            face.ModelUnInit();
+            face=null;
+        }
+        if (cameraDevice != null) {
+            cameraDevice.close();
+            cameraDevice = null;
+        }
+        if (imageReader != null) {
+            imageReader.close();
+            imageReader = null;
+        }
+
+    }
+}

+ 29 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/ToastUtil.java

@@ -0,0 +1,29 @@
+package com.oumasoft.facelibrary;
+
+import android.widget.Toast;
+import cn.gxeea.zk.MyApplication;
+
+
+public class ToastUtil {
+	private static Toast toast;//单例的吐司
+	public static void show(String text){
+		if(toast==null){
+			toast = Toast.makeText(MyApplication.getContext(), text,Toast.LENGTH_SHORT);
+//			try {	//修改Toast的字体样式
+//				LinearLayout linearLayout = (LinearLayout) toast.getView();
+//				TextView messageTextView = (TextView) linearLayout.getChildAt(0);
+//				messageTextView.setTypeface(TextFontUtils.getFace());
+//			} catch (Exception e) {
+//				LogUtil.ShowLog("无法修改Toast的字体样式");
+//			}
+		}
+		toast.setText(text);//将文本设置给吐司
+		toast.show();
+	}
+	public static void cancelToast(){
+		if(toast != null) {
+			toast.cancel();
+			toast = null;
+		}
+	}
+}

+ 105 - 0
ses-app/ses-app-android/app/src/main/java/com/oumasoft/facelibrary/ViewUtil.java

@@ -0,0 +1,105 @@
+package com.oumasoft.facelibrary;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+/**
+ * @author wangcccong
+ * @version 1.140122
+ * create at:2014-02-26
+ */
+public class ViewUtil {
+ 
+    /**
+     * 获取屏幕的宽度
+     * @param context
+     * @return
+     */
+    public static int getScreenWidth(Context context) {
+        Resources res = context.getResources();
+        return res.getDisplayMetrics().widthPixels;
+    }
+     
+    /**
+     * 获取屏幕高度
+     * @param context
+     * @return
+     */
+    public static int getScreenHeight(Context context) {
+        Resources res = context.getResources();
+        return res.getDisplayMetrics().heightPixels;
+    }
+
+    /**
+     * 描述:根据分辨率获得字体大小.
+     *
+     * @param screenWidth the screen width
+     * @param screenHeight the screen height
+     * @param textSize the text size
+     * @return the int
+     */
+    public static int resizeTextSize(int screenWidth,int screenHeight,int textSize){
+        float ratio =  1;
+        try {
+            float ratioWidth = (float)screenWidth / 480; 
+            float ratioHeight = (float)screenHeight / 800; 
+            ratio = Math.min(ratioWidth, ratioHeight); 
+        } catch (Exception e) {
+        }
+        return Math.round(textSize * ratio);
+    }
+     
+    /**
+     * 
+     * 描述:dip转换为px
+     * @param context
+     * @param dipValue
+     * @return
+     * @throws 
+     */
+    public static int dip2px(Context context, float dipValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return Math.round(dipValue * scale);
+    }
+ 
+    /**
+     * 
+     * 描述:px转换为dip
+     * @param context
+     * @param pxValue
+     * @return
+     * @throws 
+     */
+    public static int px2dip(Context context, float pxValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return Math.round(pxValue / scale);
+    }
+     
+    /**
+     * 
+     * 描述:px转换为sp
+     * @param context
+     * @param pxValue
+     * @return
+     * @throws 
+     */
+    public static int px2sp(Context context, float pxValue) {
+        final float scale = context.getResources().getDisplayMetrics().scaledDensity;
+        return Math.round(pxValue / scale);
+    }
+     
+    /**
+     * 
+     * 描述:sp转换为px
+     * @param context
+     * @param spValue
+     * @return
+     * @throws 
+     */
+    public static int sp2px(Context context, float spValue) {
+        final float scale = context.getResources().getDisplayMetrics().scaledDensity;
+        return Math.round(spValue * scale);
+    }
+
+
+}

+ 365 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/AbstractCommonMotionLivingActivity.java

@@ -0,0 +1,365 @@
+package com.turui.liveness.motion;
+
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import cn.gxeea.zk.R;
+import com.sensetime.senseid.sdk.liveness.interactive.InteractiveLivenessApi;
+import com.sensetime.senseid.sdk.liveness.interactive.MotionComplexity;
+import com.sensetime.senseid.sdk.liveness.interactive.NativeMotion;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.PixelFormat;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.ResultCode;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.Size;
+import com.sensetime.senseid.sdk.liveness.interactive.common.util.FileUtil;
+import com.sensetime.senseid.sdk.liveness.interactive.type.BoundInfo;
+import com.turui.liveness.motion.fragment.MotionStepControlFragment;
+import com.turui.liveness.motion.type.StepBean;
+import com.turui.liveness.motion.ui.camera.SenseCamera;
+import com.turui.liveness.motion.ui.camera.SenseCameraPreview;
+import com.turui.liveness.motion.util.MediaController;
+import com.turui.liveness.motion.util.ToastUtil;
+import com.turui.liveness.motion.view.AbstractOverlayView;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+abstract class AbstractCommonMotionLivingActivity extends AppCompatActivity
+        implements Camera.PreviewCallback {
+
+    public static final String EXTRA_DIFFICULTY = "extra_difficulty";
+    public static final String EXTRA_VOICE = "extra_voice";
+    public static final String EXTRA_SEQUENCES = "extra_sequences";
+    public static final String RESULT_DEAL_ERROR_INNER = "result_deal_error_inner";
+    public static final String RESULT_SDK_ERROR_CODE = "result_sdk_error_code";
+    public static final String RESULT_CLOUD_INTERNAL_ERROR = "result_cloud_internal_error";
+
+    public static final int CANCEL_INITIATIVE = 0x101;
+
+    protected static final String DETECTION_MODEL_FILE_NAME = "M_Detect_Hunter_SmallFace.model";
+    protected static final String ALIGNMENT_MODEL_FILE_NAME = "M_Align_occlusion.model";
+    protected static final String QUALITY_MODEL_FILE_NAME = "M_Face_Quality_Assessment.model";
+    protected static final String FRAME_SELECTOR_MODEL_FILE_NAME = "M_Liveness_Cnn_half.model";
+
+    protected static final String LICENSE_FILE_NAME = "SenseID_Liveness_Interactive.lic";
+    protected final List<StepBean> mCurrentStepBeans = new ArrayList<>();
+    protected boolean mIsVoiceOn = true;
+    protected int mDifficulty = MotionComplexity.NORMAL;
+    protected int[] mSequences = new int[]{
+            NativeMotion.CV_LIVENESS_BLINK, NativeMotion.CV_LIVENESS_MOUTH,
+            NativeMotion.CV_LIVENESS_HEADNOD, NativeMotion.CV_LIVENESS_HEADYAW
+    };
+    protected int mCurrentMotionIndex = -1;
+    protected boolean mStartInputData = false;
+    protected boolean mIsCanceled = true;
+
+    protected TextView mTipsView = null;
+    protected View mLoadingView = null;
+    protected AbstractOverlayView mOverlayView = null;
+    protected MotionStepControlFragment mMotionStepControlFragment;
+
+    protected SenseCameraPreview mCameraPreviewView = null;
+    protected SenseCamera mSenseCamera = null;
+    protected boolean mMotionDetecting;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+
+        super.onCreate(savedInstanceState);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+            uiFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            getWindow().setStatusBarColor(Color.TRANSPARENT);
+            getWindow().getDecorView().setSystemUiVisibility(uiFlags);
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            Window window = getWindow();
+            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+                                      | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            window.setStatusBarColor(Color.TRANSPARENT);
+        }
+
+        File protobufFolder = new File(this.getFilesDir(), "protobuf");
+
+        if (protobufFolder != null && protobufFolder.exists()) {
+            FileUtil.deleteResultDir(protobufFolder.getAbsolutePath());
+        }
+    }
+
+    @Override
+    protected void onResume() {
+
+        super.onResume();
+
+        try {
+            this.mCameraPreviewView.start(this.mSenseCamera);
+            this.mSenseCamera.setOnPreviewFrameCallback(this);
+        } catch (Exception e) {
+            this.setResult(ActivityUtils.RESULT_CODE_CAMERA_ERROR);
+            this.finish();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+
+        mStartInputData = false;
+        if (mLoadingView != null) {
+            mLoadingView.clearAnimation();
+            mLoadingView.setVisibility(View.GONE);
+        }
+        InteractiveLivenessApi.release();
+        MediaController.getInstance().release();
+
+        this.mCameraPreviewView.stop();
+        this.mCameraPreviewView.release();
+
+        if (this.mIsCanceled) {
+            this.mIsCanceled = false;
+            setResult(CANCEL_INITIATIVE);
+        }
+
+        if (!isFinishing()) {
+            finish();
+        }
+
+        super.onPause();
+    }
+
+    @Override
+    protected void onDestroy() {
+
+        super.onDestroy();
+    }
+
+    @Override
+    public void onPreviewFrame(byte[] data, Camera camera) {
+
+        if (!this.mStartInputData) {
+            return;
+        }
+
+        int viewWidth = this.mCameraPreviewView.getWidth();
+        int viewHeight = this.mCameraPreviewView.getHeight();
+        Rect containerRect = this.mCameraPreviewView.convertViewRectToPicture(
+                new Rect(0, 0, viewWidth, viewHeight));
+
+        Rect maskBounds = this.mOverlayView.getMaskBounds();
+
+        if (maskBounds != null) {
+            final int imageWidth = this.mSenseCamera.getPreviewSize().getWidth();
+            final int imageHeight = this.mSenseCamera.getPreviewSize().getHeight();
+
+            BoundInfo boundInfo = this.mCameraPreviewView.convertBoundInfoToPicture(
+                    new BoundInfo(maskBounds.centerX(), maskBounds.centerY(),
+                                  maskBounds.width() * 3 / 8));
+
+            InteractiveLivenessApi.inputData(data, PixelFormat.NV21,
+                                             new Size(imageWidth, imageHeight), containerRect, true,
+                                             mSenseCamera.getRotationDegrees(), boundInfo);
+        }
+    }
+
+    /**
+     * 本地存图方法.
+     *
+     * @param protobuf    protobuf
+     * @param imageResult image result
+     */
+    protected void saveData(final byte[] protobuf, final List<byte[]> imageResult) {
+
+        // 存储protobuf.
+        if (protobuf != null && protobuf.length > 0) {
+            final String protobufPath = new File(this.getFilesDir(), "protobuf"
+                    + File.separator
+                    + "motion_liveness_result_protobuf").getAbsolutePath();
+            FileUtil.saveDataToFile(protobuf, protobufPath);
+        }
+
+        // 存检测结果图.
+        if (imageResult != null && !imageResult.isEmpty()) {
+            File imagesFolder = new File(this.getFilesDir(), "images");
+            for (int index = 0; index < imageResult.size(); index++) {
+                FileUtil.saveDataToFile(imageResult.get(index),
+                                        imagesFolder.getAbsolutePath() + File.separator + index + ".jpg");
+            }
+        }
+    }
+
+    protected boolean checkPermission(String... permissions) {
+
+        if (permissions == null || permissions.length < 1) {
+            return true;
+        }
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return true;
+        }
+        for (String permission : permissions) {
+            if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Show error message.
+     *
+     * @param errorMessage 信息内容
+     */
+    protected void showError(final String errorMessage) {
+
+        runOnUiThread(new Runnable() {
+
+            @Override
+            public void run() {
+
+                if (!TextUtils.isEmpty(errorMessage)) {
+                    ToastUtil.show(AbstractCommonMotionLivingActivity.this, errorMessage);
+                }
+            }
+        });
+    }
+
+    protected int[] getRandomSequences() {
+
+        int[] sequences = new int[3];
+        sequences[0] = NativeMotion.CV_LIVENESS_BLINK;
+        Random random = new Random();
+        int second = random.nextInt(3);
+        int third = random.nextInt(3);
+        while (third == second) {
+            third = random.nextInt(3);
+        }
+        sequences[1] = getSequenceByIndex(second);
+        sequences[2] = getSequenceByIndex(third);
+
+        return sequences;
+    }
+
+    protected int getSequenceByIndex(int index) {
+
+        switch (index) {
+            case 0:
+                return NativeMotion.CV_LIVENESS_MOUTH;
+            case 1:
+                return NativeMotion.CV_LIVENESS_HEADNOD;
+            case 2:
+                return NativeMotion.CV_LIVENESS_HEADYAW;
+            default:
+                return NativeMotion.CV_LIVENESS_BLINK;
+        }
+    }
+
+    protected String getMotionName(final int nativeMotion) {
+
+        switch (nativeMotion) {
+            case NativeMotion.CV_LIVENESS_BLINK:
+                return getResources().getString(R.string.common_blink_tag);
+            case NativeMotion.CV_LIVENESS_HEADNOD:
+                return getResources().getString(R.string.common_nod_tag);
+            case NativeMotion.CV_LIVENESS_HEADYAW:
+                return getResources().getString(R.string.common_yaw_tag);
+            case NativeMotion.CV_LIVENESS_MOUTH:
+                return getResources().getString(R.string.common_mouth_tag);
+            default:
+                return null;
+        }
+    }
+
+    protected String getMotionDescription(final int nativeMotion) {
+
+        switch (nativeMotion) {
+            case NativeMotion.CV_LIVENESS_BLINK:
+                return getResources().getString(R.string.common_blink_description);
+            case NativeMotion.CV_LIVENESS_HEADNOD:
+                return getResources().getString(R.string.common_nod_description);
+            case NativeMotion.CV_LIVENESS_HEADYAW:
+                return getResources().getString(R.string.common_yaw_description);
+            case NativeMotion.CV_LIVENESS_MOUTH:
+                return getResources().getString(R.string.common_mouth_description);
+            default:
+                return null;
+        }
+    }
+
+    protected String getErrorNotice(ResultCode resultCode) {
+
+        switch (resultCode) {
+            case STID_E_UNSAFE_ENVIRONMENT:
+                return getResources().getString(R.string.common_error_unsafe);
+            case STID_E_CALL_API_IN_WRONG_STATE:
+                return getResources().getString(R.string.common_error_wrong_state);
+            case STID_E_LICENSE_INVALID:
+                return getResources().getString(R.string.common_error_check_license_fail);
+            case STID_E_LICENSE_FILE_NOT_FOUND:
+                return getResources().getString(R.string.common_error_license_file_not_found);
+            case STID_E_LICENSE_PLATFORM_NOT_SUPPORTED:
+                return getResources().getString(R.string.common_error_platform_not_support);
+            case STID_E_LICENSE_VERSION_MISMATCH:
+                return getResources().getString(R.string.common_error_sdk_not_match);
+            case STID_E_LICENSE_BUNDLE_ID_INVALID:
+                return getResources().getString(
+                        R.string.common_error_license_package_name_mismatch);
+            case STID_E_LICENSE_EXPIRE:
+                return getResources().getString(R.string.common_error_license_expire);
+            case STID_E_MODEL_INVALID:
+                return getResources().getString(R.string.common_error_check_model_fail);
+            case STID_E_MODEL_EXPIRE:
+                return getResources().getString(R.string.common_error_model_expire);
+            case STID_E_MODEL_FILE_NOT_FOUND:
+                return getResources().getString(R.string.common_error_model_file_not_found);
+            case STID_E_TIMEOUT:
+                return getResources().getString(R.string.common_error_error_time_out);
+            case STID_E_SERVER_ACCESS:
+                return getResources().getString(R.string.common_error_error_server);
+            case STID_E_CHECK_CONFIG_FAIL:
+                return getResources().getString(R.string.common_error_check_config_fail);
+            case STID_E_NOFACE_DETECTED:
+                return getResources().getString(R.string.common_error_action_over);
+            case STID_E_DETECT_FAIL:
+            case STID_E_HACK:
+                return getResources().getString(R.string.common_error_interactive_detection_fail);
+            case STID_E_SERVER_TIMEOUT:
+                return getResources().getString(R.string.common_error_server_timeout);
+            case STID_E_FACE_COVERED:
+                return getResources().getString(R.string.common_error_face_covered);
+            case STID_E_FACE_LIGHT_DARK:
+                return getResources().getString(R.string.common_face_light_dark_detect);
+            case STID_E_INVALID_ARGUMENTS:
+                return getResources().getString(R.string.common_error_invalid_arguments);
+            case STID_E_DETECTION_MODEL_FILE_NOT_FOUND:
+                return getResources().getString(R.string.common_error_detection_model_not_found);
+            case STID_E_ALIGNMENT_MODEL_FILE_NOT_FOUND:
+                return getResources().getString(R.string.common_error_alignment_model_not_found);
+            case STID_E_FACE_QUALITY_MODEL_FILE_NOT_FOUND:
+                return getResources().getString(R.string.common_error_face_quality_model_not_found);
+            case STID_E_FRAME_SELECTOR_MODEL_FILE_NOT_FOUND:
+                return getResources().getString(R.string.common_error_frame_select_model_not_found);
+            case STID_E_ANTI_SPOOFING_MODEL_FILE_NOT_FOUND:
+                return getResources().getString(
+                        R.string.common_error_anti_spoofing_model_not_found);
+            case STID_E_CAPABILITY_NOT_SUPPORTED:
+                return getResources().getString(R.string.common_error_capability_not_support);
+            default:
+                return null;
+        }
+    }
+
+    //sample codes used for re begin.
+    abstract void rebegin(final ResultCode resultCode);
+}

+ 94 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ActivityUtils.java

@@ -0,0 +1,94 @@
+package com.turui.liveness.motion;
+
+import android.app.Activity;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.ResultCode;
+
+/**
+ * Created on 2018/03/13.
+ *
+ * @author Zhu Xiangdong
+ */
+public class ActivityUtils {
+    public static final int RESULT_CODE_NO_PERMISSIONS = Activity.RESULT_FIRST_USER + 1;
+    public static final int RESULT_CODE_CAMERA_ERROR = Activity.RESULT_FIRST_USER + 2;
+    public static final int RESULT_CODE_LICENSE_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 3;
+    public static final int RESULT_CODE_WRONG_STATE = Activity.RESULT_FIRST_USER + 4;
+    public static final int RESULT_CODE_LICENSE_EXPIRE = Activity.RESULT_FIRST_USER + 5;
+    public static final int RESULT_CODE_LICENSE_PACKAGE_NAME_MISMATCH = Activity.RESULT_FIRST_USER + 6;
+    public static final int RESULT_CODE_CHECK_LICENSE_FAIL = Activity.RESULT_FIRST_USER + 7;
+    public static final int RESULT_CODE_TIMEOUT = Activity.RESULT_FIRST_USER + 8;
+    public static final int RESULT_CODE_CHECK_MODEL_FAIL = Activity.RESULT_FIRST_USER + 9;
+    public static final int RESULT_CODE_MODEL_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 10;
+    public static final int RESULT_CODE_MODEL_EXPIRE = Activity.RESULT_FIRST_USER + 12;
+    public static final int RESULT_CODE_SERVER = Activity.RESULT_FIRST_USER + 13;
+    public static final int RESULT_CODE_DETECT_FAIL = Activity.RESULT_FIRST_USER + 14;
+    public static final int RESULT_CODE_FACE_STATE = Activity.RESULT_FIRST_USER + 15;
+    public static final int RESULT_CODE_SDK_VERSION_MISMATCH = Activity.RESULT_FIRST_USER + 16;
+    public static final int RESULT_CODE_PLATFORM_NOTSUPPORTED = Activity.RESULT_FIRST_USER + 17;
+    public static final int RESULT_CODE_FACE_COVERED = Activity.RESULT_FIRST_USER + 18;
+    public static final int RESULT_CODE_NETWORK_TIMEOUT = Activity.RESULT_FIRST_USER + 19;
+    public static final int RESULT_CODE_INVALID_ARGUMENTS = Activity.RESULT_FIRST_USER + 20;
+    public static final int RESULT_CODE_NETWORK_UNAVAILABLE = Activity.RESULT_FIRST_USER + 21;
+    public static final int RESULT_CODE_DETECTION_MODEL_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 22;
+    public static final int RESULT_CODE_ALIGNMENT_MODEL_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 23;
+    public static final int RESULT_CODE_FRAME_SELECT_MODEL_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 24;
+    public static final int RESULT_CODE_ANTI_SPOOFING_MODEL_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 25;
+    public static final int RESULT_CODE_FACE_QUALITY_MODEL_FILE_NOT_FOUND = Activity.RESULT_FIRST_USER + 26;
+    public static final int RESULT_CODE_CAPABILITY_NOT_SUPPORTED = Activity.RESULT_FIRST_USER + 27;
+
+    static int convertResultCode(final ResultCode resultCode) {
+        switch (resultCode) {
+            case STID_E_LICENSE_FILE_NOT_FOUND:
+                return RESULT_CODE_LICENSE_FILE_NOT_FOUND;
+            case STID_E_CALL_API_IN_WRONG_STATE:
+                return RESULT_CODE_WRONG_STATE;
+            case STID_E_LICENSE_EXPIRE:
+                return RESULT_CODE_LICENSE_EXPIRE;
+            case STID_E_LICENSE_VERSION_MISMATCH:
+                return RESULT_CODE_SDK_VERSION_MISMATCH;
+            case STID_E_LICENSE_PLATFORM_NOT_SUPPORTED:
+                return RESULT_CODE_PLATFORM_NOTSUPPORTED;
+            case STID_E_LICENSE_BUNDLE_ID_INVALID:
+                return RESULT_CODE_LICENSE_PACKAGE_NAME_MISMATCH;
+            case STID_E_LICENSE_INVALID:
+                return RESULT_CODE_CHECK_LICENSE_FAIL;
+            case STID_E_MODEL_INVALID:
+                return RESULT_CODE_CHECK_MODEL_FAIL;
+            case STID_E_MODEL_FILE_NOT_FOUND:
+                return RESULT_CODE_MODEL_FILE_NOT_FOUND;
+            case STID_E_DETECTION_MODEL_FILE_NOT_FOUND:
+                return RESULT_CODE_DETECTION_MODEL_FILE_NOT_FOUND;
+            case STID_E_ALIGNMENT_MODEL_FILE_NOT_FOUND:
+                return RESULT_CODE_ALIGNMENT_MODEL_FILE_NOT_FOUND;
+            case STID_E_FRAME_SELECTOR_MODEL_FILE_NOT_FOUND:
+                return RESULT_CODE_FRAME_SELECT_MODEL_FILE_NOT_FOUND;
+            case STID_E_ANTI_SPOOFING_MODEL_FILE_NOT_FOUND:
+                return RESULT_CODE_ANTI_SPOOFING_MODEL_FILE_NOT_FOUND;
+            case STID_E_FACE_QUALITY_MODEL_FILE_NOT_FOUND:
+                return RESULT_CODE_FACE_QUALITY_MODEL_FILE_NOT_FOUND;
+            case STID_E_MODEL_EXPIRE:
+                return RESULT_CODE_MODEL_EXPIRE;
+            case STID_E_TIMEOUT:
+                return RESULT_CODE_TIMEOUT;
+            case STID_E_SERVER_ACCESS:
+                return RESULT_CODE_SERVER;
+            case STID_E_HACK:
+            case STID_E_DETECT_FAIL:
+            case STID_E_CHECK_CONFIG_FAIL:
+            case STID_E_UNSAFE_ENVIRONMENT:
+                return RESULT_CODE_DETECT_FAIL;
+            case STID_E_NOFACE_DETECTED:
+                return RESULT_CODE_FACE_STATE;
+            case STID_E_FACE_COVERED:
+                return RESULT_CODE_FACE_COVERED;
+            case STID_E_SERVER_TIMEOUT:
+                return RESULT_CODE_NETWORK_TIMEOUT;
+            case STID_E_INVALID_ARGUMENTS:
+                return RESULT_CODE_INVALID_ARGUMENTS;
+            case STID_E_CAPABILITY_NOT_SUPPORTED:
+                return RESULT_CODE_CAPABILITY_NOT_SUPPORTED;
+            default:
+                return Activity.RESULT_OK;
+        }
+    }
+}

+ 79 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ImageManager.java

@@ -0,0 +1,79 @@
+package com.turui.liveness.motion;
+
+
+import androidx.annotation.NonNull;
+import com.sensetime.senseid.sdk.liveness.interactive.LivenessResult;
+import com.sensetime.senseid.sdk.liveness.interactive.SignedObject;
+import com.sensetime.senseid.sdk.liveness.interactive.VerifiedFrame;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created on 2020-01-08.
+ *
+ * @author Qiang Lili
+ */
+public class ImageManager {
+
+    private List<byte[]> mImageListData = new ArrayList<>();
+    private List<byte[]> mCroppedImages = new ArrayList<>();
+
+    private ImageManager() {
+        // empty.
+    }
+
+    /**
+     * Get instance of ImageManager.
+     *
+     * @return instance of ImageManager.
+     */
+    public static ImageManager getInstance() {
+        return InstanceHolder.INSTANCE;
+    }
+
+    /**
+     * Save image result.
+     *
+     * @param livenessResult result.
+     */
+    public void cacheImages(@NonNull final LivenessResult livenessResult) {
+        this.mImageListData.clear();
+        this.mCroppedImages.clear();
+
+        if (livenessResult.getFrameList() != null && !livenessResult.getFrameList().isEmpty()) {
+            for (VerifiedFrame verifiedFrame : livenessResult.getFrameList()) {
+                this.mImageListData.add(verifiedFrame.getImage().getContent());
+            }
+        }
+
+        if (null != livenessResult.getCroppedImages() && !livenessResult.getCroppedImages()
+                .isEmpty()) {
+            for (SignedObject signedObject : livenessResult.getCroppedImages()) {
+                this.mCroppedImages.add(signedObject.getContent());
+            }
+        }
+    }
+
+    /**
+     * Get image result data.
+     *
+     * @return image result data
+     */
+    public List<byte[]> getImageResult() {
+        return this.mImageListData;
+    }
+
+    /**
+     * Get cropped images.
+     *
+     * @return Cropped images.
+     */
+    public List<byte[]> getCroppedImages() {
+        return mCroppedImages;
+    }
+
+    public static class InstanceHolder {
+        private static final ImageManager INSTANCE = new ImageManager();
+    }
+}

+ 386 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/MotionLivenessActivity.java

@@ -0,0 +1,386 @@
+package com.turui.liveness.motion;
+
+import android.Manifest;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.LinearInterpolator;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import cn.gxeea.zk.R;
+import com.sensetime.senseid.sdk.liveness.interactive.*;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.CloudInternalCode;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.ResultCode;
+import com.sensetime.senseid.sdk.liveness.interactive.common.util.FileUtil;
+import com.sensetime.senseid.sdk.liveness.interactive.type.FaceDistance;
+import com.sensetime.senseid.sdk.liveness.interactive.type.FacePosition;
+import com.sensetime.senseid.sdk.liveness.interactive.type.LightIntensity;
+import com.sensetime.senseid.sdk.liveness.interactive.type.OcclusionStatus;
+import com.turui.liveness.motion.fragment.MotionStepControlFragment;
+import com.turui.liveness.motion.type.StepBean;
+import com.turui.liveness.motion.ui.camera.SenseCamera;
+import com.turui.liveness.motion.util.MediaController;
+import com.turui.liveness.motion.util.NetworkUtil;
+
+import java.io.File;
+import java.util.List;
+
+public class MotionLivenessActivity extends AbstractCommonMotionLivingActivity {
+
+    private OnAdvancedLivenessListener mLivenessListener = new OnAdvancedLivenessListener() {
+
+        private long mLastStatusUpdateTime;
+
+        @Override
+        public void onInitialized() {
+            mStartInputData = true;
+
+            // 开启质检检测开关的示例代码(注:init时传入正确的质检模型才会生效)
+            //InteractiveLivenessApi.setIlluminationFilterEnable(true, 1.899F, 4.9970F);
+            //InteractiveLivenessApi.setBlurryFilterEnable(false, 0.4F);
+            // 开启眉毛遮挡(只对对准阶段有效)
+            //InteractiveLivenessApi.setBrowOcclusion(true);
+
+            //开启选帧条件的示例代码
+            //创建builder
+            //FaceSelectFilters.Builder builder=new FaceSelectFilters.Builder();
+            //建议启用睁闭眼过滤时设置为0.47,检测结果大于该阈值时判断为睁眼
+            //builder.setEyeEnable(true,0.47f);  //设置相应配置
+            //生成FaceSelectFilters并赋值给InteractiveLivenessApi
+            //InteractiveLivenessApi.setFaceSelectFilters(builder.build());
+        }
+
+        @Override
+        public void onStatusUpdate(final int faceState, final FaceOcclusion faceOcclusion,
+                final int faceDistance, final int lightIntensity) {
+            if (!mMotionDetecting
+                    && SystemClock.elapsedRealtime() - mLastStatusUpdateTime < 300
+                    && faceState != FacePosition.NORMAL) {
+                return;
+            }
+
+            if (faceDistance == FaceDistance.TOO_CLOSE) {
+                mTipsView.setText(R.string.common_face_too_close);
+            } else if (faceDistance == FaceDistance.TOO_FAR) {
+                mTipsView.setText(R.string.common_face_too_far);
+            } else if (faceState == FacePosition.OUT_OF_BOUND) {
+                mTipsView.setText(R.string.common_tracking_missed);
+            } else if (lightIntensity == LightIntensity.TOO_DARK) {
+                MotionLivenessActivity.this.mTipsView.setText(
+                        R.string.common_face_light_dark_align);
+            } else if (lightIntensity == LightIntensity.TOO_BRIGHT) {
+                MotionLivenessActivity.this.mTipsView.setText(
+                        R.string.common_face_light_bright_align);
+            } else if (faceOcclusion != null && faceOcclusion.isOcclusion()) {
+                StringBuffer occlusionPart = new StringBuffer();
+                boolean needComma = false;
+
+                if (faceOcclusion.getBrowOcclusionStatus() == OcclusionStatus.OCCLUSION) {
+                    occlusionPart.append(getString(R.string.common_covered_brow));
+                    needComma = true;
+                }
+
+                if (faceOcclusion.getEyeOcclusionStatus() == OcclusionStatus.OCCLUSION) {
+                    occlusionPart.append(getString(R.string.common_covered_eye));
+                    needComma = true;
+                }
+
+                if (faceOcclusion.getNoseOcclusionStatus() == OcclusionStatus.OCCLUSION) {
+                    occlusionPart.append(needComma ? "、" : "");
+                    occlusionPart.append(getString(R.string.common_covered_nose));
+                    needComma = true;
+                }
+
+                if (faceOcclusion.getMouthOcclusionStatus() == OcclusionStatus.OCCLUSION) {
+                    occlusionPart.append(needComma ? "、" : "");
+                    occlusionPart.append(getString(R.string.common_covered_mouth));
+                }
+
+                mTipsView.setText(
+                        getString(R.string.common_face_covered, occlusionPart.toString()));
+            } else if (faceState == FacePosition.NORMAL) {
+                mTipsView.setText(
+                        mMotionDetecting ? getMotionDescription(mSequences[mCurrentMotionIndex])
+                                : getString(R.string.common_detecting));
+            } else {
+                mTipsView.setText(R.string.common_tracking_missed);
+            }
+
+            mLastStatusUpdateTime = SystemClock.elapsedRealtime();
+        }
+
+        @Override
+        public void onFailure(ResultCode resultCode, int httpStatusCode, int cloudInternalCode,
+                String requestId, byte[] bytes, List<byte[]> imageData) {
+            // deprecated.
+        }
+
+        @Override
+        public void onFailure(LivenessResult livenessResult, CloudInfo cloudInfo) {
+            MotionLivenessActivity.this.mStartInputData = false;
+            MotionLivenessActivity.this.onFailDetect(livenessResult.getResultCode(),
+                    cloudInfo.getCloudCode());
+        }
+
+        @Override
+        public void onSuccess(LivenessResult livenessResult, CloudInfo cloudInfo) {
+            MotionLivenessActivity.this.mStartInputData = false;
+
+            // Code for saving data.
+            //saveData(result, imageData);
+
+            ImageManager.getInstance().cacheImages(livenessResult);
+
+            final Intent data = new Intent();
+            data.putExtra(RESULT_DEAL_ERROR_INNER, false);
+            setResult(RESULT_OK, data);
+            finish();
+        }
+
+        @Override
+        public void onSuccess(int httpCode, int cloudInternalCode, String requestId, byte[] result,
+                List<byte[]> imageData) {
+            // deprecated.
+        }
+
+        @Override
+        public void onOnlineCheckBegin() {
+            MotionLivenessActivity.this.mStartInputData = false;
+
+            MotionLivenessActivity.this.mCameraPreviewView.setVisibility(View.GONE);
+            MotionLivenessActivity.this.mOverlayView.setVisibility(View.GONE);
+            MotionLivenessActivity.this.mLoadingView.setVisibility(View.VISIBLE);
+
+            if (MotionLivenessActivity.this.mIsVoiceOn) {
+                MediaController.getInstance().release();
+            }
+
+            Animation animation =
+                    AnimationUtils.loadAnimation(MotionLivenessActivity.this, R.anim.anim_rotate);
+            animation.setInterpolator(new LinearInterpolator());
+            MotionLivenessActivity.this.mLoadingView.startAnimation(animation);
+        }
+
+        @Override
+        public void onAligned() {
+            MotionLivenessActivity.this.mOverlayView.setMaskPathColor(
+                    mOverlayView.getResources().getColor(R.color.common_interaction_ginger_yellow));
+            MotionLivenessActivity.this.mTipsView.setText(null);
+
+            InteractiveLivenessApi.start(mSequences, mDifficulty);
+        }
+
+        @Override
+        public void onMotionSet(final int index, int motion) {
+            MotionLivenessActivity.this.mCurrentMotionIndex = index;
+
+            MotionLivenessActivity.this.mMotionStepControlFragment.updateMotionStep(
+                    mCurrentMotionIndex, StepBean.StepState.STEP_CURRENT);
+
+            if (MotionLivenessActivity.this.mCurrentMotionIndex > 0) {
+                MotionLivenessActivity.this.mMotionStepControlFragment.updateMotionStep(
+                        mCurrentMotionIndex - 1, StepBean.StepState.STEP_COMPLETED);
+            }
+
+            MotionLivenessActivity.this.mTipsView.setText(
+                    getMotionDescription(mSequences[mCurrentMotionIndex]));
+            MotionLivenessActivity.this.mMotionDetecting = true;
+
+            if (MotionLivenessActivity.this.mIsVoiceOn) {
+                MediaController.getInstance()
+                        .playNotice(MotionLivenessActivity.this, mSequences[mCurrentMotionIndex]);
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (!checkPermission(Manifest.permission.CAMERA)) {
+            final Intent data = new Intent();
+            data.putExtra(RESULT_DEAL_ERROR_INNER, true);
+            showError(getString(R.string.common_error_no_camera_permission));
+            setResult(ActivityUtils.RESULT_CODE_NO_PERMISSIONS, data);
+            finish();
+            return;
+        }
+
+        if (!NetworkUtil.isNetworkConnected(MotionLivenessActivity.this)) {
+            final Intent data = new Intent();
+            data.putExtra(RESULT_DEAL_ERROR_INNER, true);
+            showError(getString(R.string.common_error_no_internet_permission));
+            setResult(ActivityUtils.RESULT_CODE_NETWORK_UNAVAILABLE, data);
+            finish();
+            return;
+        }
+
+        setContentView(R.layout.common_activity_liveness_motion);
+        // init setting values.
+        this.mIsVoiceOn = getIntent().getBooleanExtra(EXTRA_VOICE, true);
+        this.mDifficulty = getIntent().getIntExtra(EXTRA_DIFFICULTY, MotionComplexity.NORMAL);
+
+        int[] sequences = getIntent().getIntArrayExtra(EXTRA_SEQUENCES);
+        if (sequences != null && sequences.length > 0) {
+            this.mSequences = sequences;
+        }
+        for (int nativeMotion : this.mSequences) {
+            this.mCurrentStepBeans.add(
+                    new StepBean(getMotionName(nativeMotion), StepBean.StepState.STEP_UNDO));
+        }
+
+        findViewById(R.id.img_back).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                //Codes for normal detection.
+                /*start*/
+                setResult(RESULT_CANCELED);
+                finish();
+                /*end*/
+
+                //Codes for rebegin.
+                /*start
+                MotionLivenessActivity.this.rebegin(null);
+                end*/
+            }
+        });
+        ((TextView) findViewById(R.id.txt_title)).setText(
+                getString(R.string.common_interactive_liveness));
+
+        this.mOverlayView = findViewById(R.id.overlay_interactive);
+        this.mTipsView = (TextView) findViewById(R.id.tips);
+        this.mLoadingView = findViewById(R.id.img_loading);
+        this.mCameraPreviewView = findViewById(R.id.camera_preview);
+
+        this.mMotionStepControlFragment = MotionStepControlFragment.newInstance();
+        this.mMotionStepControlFragment.addMotionStepBeans(this.mCurrentStepBeans);
+        final FragmentManager fragmentManager = this.getSupportFragmentManager();
+        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+        fragmentTransaction.replace(R.id.layout_motion_steps, this.mMotionStepControlFragment,
+                "MotionStep");
+        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+        fragmentTransaction.commitAllowingStateLoss();
+
+        final File externalAssets = new File(this.getFilesDir(), "assets");
+
+        FileUtil.copyAssetsToFile(MotionLivenessActivity.this, DETECTION_MODEL_FILE_NAME,
+                new File(externalAssets, DETECTION_MODEL_FILE_NAME).getAbsolutePath());
+        FileUtil.copyAssetsToFile(MotionLivenessActivity.this, ALIGNMENT_MODEL_FILE_NAME,
+                new File(externalAssets, ALIGNMENT_MODEL_FILE_NAME).getAbsolutePath());
+        FileUtil.copyAssetsToFile(MotionLivenessActivity.this, QUALITY_MODEL_FILE_NAME,
+                new File(externalAssets, QUALITY_MODEL_FILE_NAME).getAbsolutePath());
+        FileUtil.copyAssetsToFile(MotionLivenessActivity.this, FRAME_SELECTOR_MODEL_FILE_NAME,
+                new File(externalAssets, FRAME_SELECTOR_MODEL_FILE_NAME).getAbsolutePath());
+
+        FileUtil.copyAssetsToFile(MotionLivenessActivity.this, LICENSE_FILE_NAME,
+                new File(externalAssets, LICENSE_FILE_NAME).getAbsolutePath());
+
+        // Sample默认不启动与人脸质量相关的质量检测(光线、模糊).
+        InteractiveLivenessApi.init(MotionLivenessActivity.this,
+                new File(externalAssets, LICENSE_FILE_NAME).getAbsolutePath(),
+                new File(externalAssets, DETECTION_MODEL_FILE_NAME).getAbsolutePath(),
+                new File(externalAssets, ALIGNMENT_MODEL_FILE_NAME).getAbsolutePath(), null,
+                new File(externalAssets, FRAME_SELECTOR_MODEL_FILE_NAME).getAbsolutePath(),
+                mLivenessListener);
+
+        // 开启质量检测需要在init时传入质检模型进行初始化,且需要调用set方法开启质量检测(
+        // 具体参照本sample中 OnLivenessListener实例中onInitialized方法内的注释代码)
+        /*InteractiveLivenessApi.init(MotionLivenessActivity.this, API_KEY, API_SECRET,
+                new File(externalAssets, LICENSE_FILE_NAME).getAbsolutePath(),
+                new File(externalAssets, DETECTION_MODEL_FILE_NAME).getAbsolutePath(),
+                new File(externalAssets, ALIGNMENT_MODEL_FILE_NAME).getAbsolutePath(),
+                new File(externalAssets, QUALITY_MODEL_FILE_NAME).getAbsolutePath(),
+                new File(externalAssets, FRAME_SELECTOR_MODEL_FILE_NAME).getAbsolutePath(),
+                mLivenessListener);*/
+
+        InteractiveLivenessApi.start(null, mDifficulty);
+
+        mSenseCamera = new SenseCamera.Builder(this).setFacing(SenseCamera.CAMERA_FACING_FRONT)
+                .setRequestedPreviewSize(640, 480)
+                .build();
+        mStartInputData = false;
+    }
+
+    @Override
+    void rebegin(ResultCode resultCode) {
+        Toast.makeText(this, getString(R.string.common_interactive_detect_again, resultCode),
+                Toast.LENGTH_SHORT).show();
+        InteractiveLivenessApi.stop();
+        this.mMotionDetecting = false;
+        this.mCurrentMotionIndex = -1;
+        if (mLoadingView != null) {
+            mLoadingView.clearAnimation();
+            mLoadingView.setVisibility(View.GONE);
+        }
+        this.mMotionStepControlFragment.resetMotionStep();
+        this.mTipsView.setText(null);
+        this.mOverlayView.setMaskPathColor(
+                mOverlayView.getResources().getColor(R.color.common_interaction_light_gray));
+        MediaController.getInstance().release();
+
+        MotionLivenessActivity.this.mCameraPreviewView.setVisibility(View.VISIBLE);
+        MotionLivenessActivity.this.mOverlayView.setVisibility(View.VISIBLE);
+        MotionLivenessActivity.this.mLoadingView.setVisibility(View.GONE);
+
+        InteractiveLivenessApi.start(null, mDifficulty);
+        this.mStartInputData = true;
+    }
+
+    private void onFailDetect(ResultCode resultCode,
+            @CloudInternalCode final int cloudInternalCode) {
+
+        //Codes for rebegin.
+        /*start
+        switch (resultCode) {
+            case STID_E_API_KEY_INVALID:
+            case STID_E_SERVER_DETECT_FAIL:
+            case STID_E_SERVER_TIMEOUT:
+            case STID_E_SERVER_ACCESS:
+            case STID_E_DETECT_FAIL:
+            case STID_E_HACK:
+                rebegin(resultCode);
+                break;
+            default:
+                this.mIsCanceled = false;
+                final Intent data = new Intent();
+                data.putExtra(RESULT_SDK_ERROR_CODE, resultCode.name());
+                data.putExtra(RESULT_CLOUD_INTERNAL_ERROR, cloudInternalCode);
+                showError(getErrorNotice(resultCode));
+                data.putExtra(RESULT_DEAL_ERROR_INNER, true);
+                setResult(ActivityUtils.convertResultCode(resultCode), data);
+                finish();
+                break;
+        }
+        end*/
+
+        //Codes for normal detection.
+        /*Start*/
+        this.mIsCanceled = false;
+        final Intent data = new Intent();
+        data.putExtra(RESULT_SDK_ERROR_CODE, resultCode.name());
+        data.putExtra(RESULT_CLOUD_INTERNAL_ERROR, cloudInternalCode);
+        switch (resultCode) {
+            case STID_E_SERVER_DETECT_FAIL:
+            case STID_E_SERVER_TIMEOUT:
+            case STID_E_SERVER_ACCESS:
+            case STID_E_DETECT_FAIL:
+            case STID_E_HACK:
+                data.putExtra(RESULT_DEAL_ERROR_INNER, false);
+                setResult(ActivityUtils.convertResultCode(resultCode), data);
+                finish();
+                break;
+            default:
+                showError(getErrorNotice(resultCode));
+                data.putExtra(RESULT_DEAL_ERROR_INNER, true);
+                setResult(ActivityUtils.convertResultCode(resultCode), data);
+                finish();
+                break;
+        }
+        /*end*/
+    }
+}

+ 210 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/fragment/MotionStepControlFragment.java

@@ -0,0 +1,210 @@
+package com.turui.liveness.motion.fragment;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import cn.gxeea.zk.R;
+import com.turui.liveness.motion.type.StepBean;
+import com.turui.liveness.motion.util.DensityUtil;
+import com.turui.liveness.motion.view.WaterRippleView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created on 28/07/2018.
+ *
+ * @author Qiang Lili
+ */
+public class MotionStepControlFragment extends Fragment {
+
+    private List<StepBean> mMotionStepBeans = new ArrayList<>();
+
+    private View mLayoutView = null;
+
+    public static MotionStepControlFragment newInstance() {
+        return new MotionStepControlFragment();
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+
+        if (this.mMotionStepBeans.isEmpty()) {
+            return null;
+        }
+
+        this.mLayoutView = inflater.inflate(getLayoutResource(), container, false);
+
+        initView();
+
+        return this.mLayoutView;
+    }
+
+    private void initView() {
+        switch (this.mMotionStepBeans.size()) {
+            case 1:
+                initFirstStep();
+                break;
+            case 2:
+                initFirstStep();
+                initSecondStep();
+                break;
+            case 3:
+                initFirstStep();
+                initSecondStep();
+                initThirdStep();
+                break;
+            case 4:
+                initFirstStep();
+                initSecondStep();
+                initThirdStep();
+                initForthStep();
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void initFirstStep() {
+        if (this.mLayoutView == null || this.mMotionStepBeans.size() < 1) {
+            return;
+        }
+        ((TextView) this.mLayoutView.findViewById(R.id.txt_step_one)).setText(this.mMotionStepBeans.get(0).getName());
+    }
+
+    private void initSecondStep() {
+        if (this.mLayoutView == null || this.mMotionStepBeans.size() < 2) {
+            return;
+        }
+        ((TextView) this.mLayoutView.findViewById(R.id.txt_step_two)).setText(this.mMotionStepBeans.get(1).getName());
+    }
+
+    private void initThirdStep() {
+        if (this.mLayoutView == null || this.mMotionStepBeans.size() < 3) {
+            return;
+        }
+        ((TextView) this.mLayoutView.findViewById(R.id.txt_step_three)).setText(this.mMotionStepBeans.get(2).getName());
+    }
+
+    private void initForthStep() {
+        if (this.mLayoutView == null || this.mMotionStepBeans.size() < 4) {
+            return;
+        }
+        ((TextView) this.mLayoutView.findViewById(R.id.txt_step_four)).setText(this.mMotionStepBeans.get(3).getName());
+    }
+
+    private int getLayoutResource() {
+        if (this.mMotionStepBeans.isEmpty()) {
+            return -1;
+        }
+        switch (this.mMotionStepBeans.size()) {
+            case 1:
+                return R.layout.layout_one_motion_step;
+            case 2:
+                return R.layout.layout_two_motion_steps;
+            case 3:
+                return R.layout.layout_three_motion_steps;
+            case 4:
+                return R.layout.layout_four_motion_steps;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * Reset motion steps.
+     */
+    public void resetMotionStep() {
+        if (this.mMotionStepBeans == null || this.mMotionStepBeans.isEmpty()) {
+            return;
+        }
+        for (int index = 0; index < this.mMotionStepBeans.size(); index++) {
+            this.updateMotionStep(index, StepBean.StepState.STEP_UNDO);
+        }
+    }
+
+    /**
+     * 更新动作状态.
+     *
+     * @param stepBeanIndex stepBeanIndex.
+     * @param currentState currentState.
+     */
+    public void updateMotionStep(final int stepBeanIndex, final StepBean.StepState currentState) {
+        if (this.mMotionStepBeans.isEmpty() || stepBeanIndex < 0 || stepBeanIndex > this.mMotionStepBeans.size() - 1) {
+            return;
+        }
+
+        switch (stepBeanIndex) {
+            case 0:
+                updateStepState((WaterRippleView) this.mLayoutView.findViewById(R.id.ripple_step_first), currentState);
+                break;
+            case 1:
+                updateStepState((WaterRippleView) this.mLayoutView.findViewById(R.id.ripple_step_second), currentState);
+                this.mLayoutView.findViewById(R.id.line_first_to_second)
+                        .setBackgroundColor(currentState == StepBean.StepState.STEP_CURRENT ? getResources().getColor(
+                                R.color.common_interaction_ginger_yellow)
+                                : getResources().getColor(R.color.common_interaction_light_gray));
+                break;
+            case 2:
+                updateStepState((WaterRippleView) this.mLayoutView.findViewById(R.id.ripple_step_third), currentState);
+                this.mLayoutView.findViewById(R.id.line_second_to_third)
+                        .setBackgroundColor(currentState == StepBean.StepState.STEP_CURRENT ? getResources().getColor(
+                                R.color.common_interaction_ginger_yellow)
+                                : getResources().getColor(R.color.common_interaction_light_gray));
+                break;
+            case 3:
+                updateStepState((WaterRippleView) this.mLayoutView.findViewById(R.id.ripple_step_fourth), currentState);
+                this.mLayoutView.findViewById(R.id.line_third_to_fourth)
+                        .setBackgroundColor(currentState == StepBean.StepState.STEP_CURRENT ? getResources().getColor(
+                                R.color.common_interaction_ginger_yellow)
+                                : getResources().getColor(R.color.common_interaction_light_gray));
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void updateStepState(final WaterRippleView waterRippleView, final StepBean.StepState stepBeanState) {
+        final float imageRatio = 28.0F / 30.0F;
+
+        switch (stepBeanState) {
+            case STEP_COMPLETED:
+                waterRippleView.stop();
+                waterRippleView.setCenterIconResource(R.drawable.common_ic_motion_step_done);
+                break;
+            case STEP_CURRENT:
+                waterRippleView.setCenterImageRatio(imageRatio);
+                waterRippleView.setRippleSpeed(17);
+                waterRippleView.setRippleStrokeWidth(DensityUtil.dip2px(getActivity().getApplicationContext(), 8.0F));
+                waterRippleView.setCenterIconResource(R.drawable.common_ic_motion_step_ing);
+                waterRippleView.start();
+                break;
+            case STEP_UNDO:
+                waterRippleView.setCenterIconResource(R.drawable.common_ic_motion_step_wait);
+                waterRippleView.stop();
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * 添加要显示的动作.
+     *
+     * @param stepBeans stepBeans.
+     */
+    public void addMotionStepBeans(final List<StepBean> stepBeans) {
+        this.mMotionStepBeans.clear();
+
+        if (stepBeans != null && !stepBeans.isEmpty()) {
+            this.mMotionStepBeans.addAll(stepBeans);
+        }
+    }
+}

+ 40 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/type/StepBean.java

@@ -0,0 +1,40 @@
+package com.turui.liveness.motion.type;
+
+/**
+ * Created on 28/07/2018.
+ *
+ * @author Qiang Lili
+ */
+public class StepBean {
+
+    private String name;
+
+    private StepState state;
+
+    public StepBean(String name, StepState state) {
+        this.name = name;
+        this.state = state;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public StepState getState() {
+        return this.state;
+    }
+
+    public void setState(StepState state) {
+        this.state = state;
+    }
+
+    public enum StepState {
+        STEP_UNDO,
+        STEP_CURRENT,
+        STEP_COMPLETED
+    }
+}

+ 409 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ui/camera/SenseCamera.java

@@ -0,0 +1,409 @@
+package com.turui.liveness.motion.ui.camera;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.Camera;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.Size;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings({ "WeakerAccess", "unused", "deprecation" })
+public class SenseCamera {
+    public static final int CAMERA_FACING_BACK = Camera.CameraInfo.CAMERA_FACING_BACK;
+
+    public static final int CAMERA_FACING_FRONT = Camera.CameraInfo.CAMERA_FACING_FRONT;
+
+    private static final int DUMMY_TEXTURE_NAME = 100;
+
+    private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
+    private final Object mCameraLock = new Object();
+    private Context mContext;
+    private Camera mCamera;
+
+    private int mFacing = CAMERA_FACING_BACK;
+
+    private int mRotation;
+
+    // Default preview size, Reference https://stackoverflow.com/a/21676309.
+    private Size mPreviewSize = new Size(640, 480);
+
+    private float mRequestedFps = 24.0f;
+    private int mRequestedPreviewWidth = 640;
+    private int mRequestedPreviewHeight = 480;
+
+    private Map<byte[], ByteBuffer> mBytesToByteBuffer = new HashMap<>();
+
+    private Camera.PreviewCallback mPreviewCallback;
+
+    private SenseCamera() {
+    }
+
+    private static int getIdForRequestedCamera(final int facing) {
+        final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+        for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
+            Camera.getCameraInfo(i, cameraInfo);
+            if (cameraInfo.facing == facing) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static SizePair selectSizePair(final Camera camera, final int desiredWidth, final int desiredHeight) {
+        final List<SizePair> validPreviewSizes = generateValidPreviewSizeList(camera);
+
+        SizePair selectedPair = null;
+        int minDiff = Integer.MAX_VALUE;
+        for (SizePair sizePair : validPreviewSizes) {
+            final Size size = sizePair.previewSize();
+            int diff = Math.abs(size.getWidth() - desiredWidth) + Math.abs(size.getHeight() - desiredHeight);
+            if (diff < minDiff) {
+                selectedPair = sizePair;
+                minDiff = diff;
+            }
+        }
+
+        return selectedPair;
+    }
+
+    private static List<SizePair> generateValidPreviewSizeList(final Camera camera) {
+        final Camera.Parameters parameters = camera.getParameters();
+        final List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
+        final List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes();
+        final List<SizePair> validPreviewSizes = new ArrayList<>();
+        for (final Camera.Size previewSize : supportedPreviewSizes) {
+            final float previewAspectRatio = (float) previewSize.width / (float) previewSize.height;
+
+            for (Camera.Size pictureSize : supportedPictureSizes) {
+                float pictureAspectRatio = (float) pictureSize.width / (float) pictureSize.height;
+                if (Math.abs(previewAspectRatio - pictureAspectRatio) < ASPECT_RATIO_TOLERANCE) {
+                    validPreviewSizes.add(new SizePair(previewSize, pictureSize));
+                    break;
+                }
+            }
+        }
+
+        if (validPreviewSizes.isEmpty()) {
+            for (Camera.Size previewSize : supportedPreviewSizes) {
+                validPreviewSizes.add(new SizePair(previewSize, null));
+            }
+        }
+
+        return validPreviewSizes;
+    }
+
+    /**
+     * release.
+     */
+    public void release() {
+        synchronized (this.mCameraLock) {
+            this.stop();
+        }
+    }
+
+    /**
+     * start.
+     */
+    @SuppressLint("MissingPermission")
+    public SenseCamera start(final SurfaceHolder surfaceHolder) throws IOException, RuntimeException {
+        synchronized (this.mCameraLock) {
+            if (this.mCamera != null) {
+                return this;
+            }
+
+            this.mCamera = createCamera();
+            this.mCamera.setPreviewDisplay(surfaceHolder);
+            this.mCamera.startPreview();
+        }
+        return this;
+    }
+
+    /**
+     * stop.
+     */
+    public void stop() {
+        synchronized (this.mCameraLock) {
+            this.mBytesToByteBuffer.clear();
+
+            if (this.mCamera != null) {
+                this.mCamera.stopPreview();
+                this.mCamera.setPreviewCallbackWithBuffer(null);
+                try {
+                    this.mCamera.setPreviewDisplay(null);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+                this.mCamera.release();
+                this.mCamera = null;
+            }
+        }
+    }
+
+    /**
+     * get preview size.
+     */
+    public Size getPreviewSize() {
+        return this.mPreviewSize;
+    }
+
+    /**
+     * get camera facing.
+     */
+    public int getCameraFacing() {
+        return this.mFacing;
+    }
+
+    private Camera createCamera() throws RuntimeException {
+        final int requestedCameraId = SenseCamera.getIdForRequestedCamera(this.mFacing);
+
+        if (requestedCameraId == -1) {
+            throw new IllegalStateException("Could not find requested camera.");
+        }
+
+        final Camera camera = Camera.open(requestedCameraId);
+
+        if (camera == null) {
+            throw new IllegalStateException("Unknown camera error");
+        }
+
+        final SizePair sizePair =
+                SenseCamera.selectSizePair(camera, this.mRequestedPreviewWidth, this.mRequestedPreviewHeight);
+
+        if (sizePair == null) {
+            throw new IllegalStateException("Could not find suitable preview size.");
+        }
+
+        final Size pictureSize = sizePair.pictureSize();
+
+        this.mPreviewSize = sizePair.previewSize();
+
+        final int[] previewFpsRange = selectPreviewFpsRange(camera, this.mRequestedFps);
+
+        if (previewFpsRange == null) {
+            throw new IllegalStateException("Could not find suitable preview frames per second range.");
+        }
+
+        final Camera.Parameters parameters = camera.getParameters();
+
+        if (pictureSize != null) {
+            parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
+        }
+
+        parameters.setPreviewSize(this.mPreviewSize.getWidth(), this.mPreviewSize.getHeight());
+        parameters.setPreviewFpsRange(previewFpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                previewFpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+        parameters.setPreviewFormat(ImageFormat.NV21);
+
+        this.setRotation(camera, parameters, requestedCameraId);
+
+        if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
+            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+        }
+
+        camera.setParameters(parameters);
+
+        camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
+            @Override
+            public void onPreviewFrame(byte[] data, Camera camera) {
+                camera.addCallbackBuffer(SenseCamera.this.mBytesToByteBuffer.get(data).array());
+
+                if (SenseCamera.this.mPreviewCallback != null) {
+                    SenseCamera.this.mPreviewCallback.onPreviewFrame(
+                            SenseCamera.this.mBytesToByteBuffer.get(data).array(), camera);
+                }
+            }
+        });
+        camera.addCallbackBuffer(this.createPreviewBuffer(this.mPreviewSize));
+
+        return camera;
+    }
+
+    /**
+     * set preview Callback.
+     *
+     * @param callback callback.
+     */
+    public void setOnPreviewFrameCallback(final Camera.PreviewCallback callback) {
+        this.mPreviewCallback = callback;
+    }
+
+    private int[] selectPreviewFpsRange(final Camera camera, final float desiredPreviewFps) {
+        final int desiredPreviewFpsScaled = (int) (desiredPreviewFps * 1000.0f);
+
+        int[] selectedFpsRange = null;
+        int minDiff = Integer.MAX_VALUE;
+        List<int[]> previewFpsRangeList = camera.getParameters().getSupportedPreviewFpsRange();
+        for (int[] range : previewFpsRangeList) {
+            int deltaMin = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
+            int deltaMax = desiredPreviewFpsScaled - range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
+            int diff = Math.abs(deltaMin) + Math.abs(deltaMax);
+            if (diff < minDiff) {
+                selectedFpsRange = range;
+                minDiff = diff;
+            }
+        }
+
+        return selectedFpsRange;
+    }
+
+    private void setRotation(final Camera camera, final Camera.Parameters parameters, final int cameraId) {
+        final WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        int degrees = 0;
+        int rotation = windowManager != null ? windowManager.getDefaultDisplay().getRotation() : Surface.ROTATION_0;
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                degrees = 0;
+                break;
+            case Surface.ROTATION_90:
+                degrees = 90;
+                break;
+            case Surface.ROTATION_180:
+                degrees = 180;
+                break;
+            case Surface.ROTATION_270:
+                degrees = 270;
+                break;
+            default:
+                break;
+        }
+
+        final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+        Camera.getCameraInfo(cameraId, cameraInfo);
+
+        int angle;
+        int displayAngle;
+        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            angle = (cameraInfo.orientation + degrees) % 360;
+            displayAngle = (360 - angle) % 360; // compensate for it being mirrored
+        } else {  // back-facing
+            angle = (cameraInfo.orientation - degrees + 360) % 360;
+            displayAngle = angle;
+        }
+
+        this.mRotation = angle / 90;
+
+        camera.setDisplayOrientation(displayAngle);
+        parameters.setRotation(angle);
+    }
+
+    /**
+     * get Rotation Degrees.
+     *
+     * @return Degrees.
+     */
+    public int getRotationDegrees() {
+        return this.mRotation * 90;
+    }
+
+    private byte[] createPreviewBuffer(Size previewSize) {
+        final int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21);
+        final long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel;
+        final int bufferSize = (int) Math.ceil(sizeInBits / 8.0d) + 1;
+
+        final byte[] byteArray = new byte[bufferSize];
+        final ByteBuffer buffer = ByteBuffer.wrap(byteArray);
+
+        if (!buffer.hasArray() || (buffer.array() != byteArray)) {
+            throw new IllegalStateException("Failed to create valid buffer for camera.");
+        }
+
+        this.mBytesToByteBuffer.put(byteArray, buffer);
+
+        return byteArray;
+    }
+
+    public static class Builder {
+        private final SenseCamera mSenseCamera = new SenseCamera();
+
+        /**
+         * Camera Builder.
+         */
+        public Builder(final Context context) {
+            if (context == null) {
+                throw new IllegalArgumentException("No context supplied.");
+            }
+
+            this.mSenseCamera.mContext = context;
+        }
+
+        /**
+         * set requested fps.
+         */
+        public Builder setRequestedFps(final float fps) {
+            if (fps <= 0) {
+                throw new IllegalArgumentException("Invalid fps: " + fps);
+            }
+
+            this.mSenseCamera.mRequestedFps = fps;
+
+            return this;
+        }
+
+        /**
+         * set requested preview size.
+         */
+        public Builder setRequestedPreviewSize(final int width, final int height) {
+            final int max = 1000000;
+
+            if ((width <= 0) || (width > max) || (height <= 0) || (height > max)) {
+                throw new IllegalArgumentException("Invalid preview size: " + width + "x" + height);
+            }
+
+            this.mSenseCamera.mRequestedPreviewWidth = width;
+            this.mSenseCamera.mRequestedPreviewHeight = height;
+
+            return this;
+        }
+
+        /**
+         * set camera facing.
+         */
+        public Builder setFacing(final int facing) {
+            if ((facing != CAMERA_FACING_BACK) && (facing != CAMERA_FACING_FRONT)) {
+                throw new IllegalArgumentException("Invalid camera: " + facing);
+            }
+
+            this.mSenseCamera.mFacing = facing;
+
+            return this;
+        }
+
+        /**
+         * build camera.
+         */
+        public SenseCamera build() {
+            return this.mSenseCamera;
+        }
+    }
+
+    private static class SizePair {
+        private Size mPreview;
+        private Size mPicture;
+
+        public SizePair(final Camera.Size previewSize, final Camera.Size pictureSize) {
+            mPreview = new Size(previewSize.width, previewSize.height);
+            if (pictureSize != null) {
+                mPicture = new Size(pictureSize.width, pictureSize.height);
+            }
+        }
+
+        public Size previewSize() {
+            return this.mPreview;
+        }
+
+        @SuppressWarnings("unused")
+        public Size pictureSize() {
+            return this.mPicture;
+        }
+    }
+}
+

+ 323 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/ui/camera/SenseCameraPreview.java

@@ -0,0 +1,323 @@
+package com.turui.liveness.motion.ui.camera;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import com.sensetime.senseid.sdk.liveness.interactive.common.type.Size;
+import com.sensetime.senseid.sdk.liveness.interactive.type.BoundInfo;
+import com.turui.liveness.motion.ActivityUtils;
+
+import java.io.IOException;
+
+/**
+ * Created on 2018/03/13.
+ *
+ * @author Zhu Xiangdong
+ */
+public class SenseCameraPreview extends ViewGroup {
+
+    public Rect scaledRect = null;
+
+    private Context mContext;
+
+    private SurfaceView mSurfaceView;
+
+    private boolean mStartRequested;
+
+    private boolean mSurfaceAvailable;
+
+    private SenseCamera mCamera;
+
+    /**
+     * SenseCameraPreview.
+     *
+     * @param context context.
+     * @param attrs attrs.
+     */
+    public SenseCameraPreview(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+
+        this.mContext = context;
+        this.mStartRequested = false;
+        this.mSurfaceAvailable = false;
+
+        this.mSurfaceView = new SurfaceView(context);
+        this.mSurfaceView.getHolder().addCallback(new SurfaceCallback());
+        addView(this.mSurfaceView);
+    }
+
+    /**
+     * start with SenseCamera.
+     *
+     * @param senseCamera senseCamera.
+     */
+    public void start(final SenseCamera senseCamera) throws IOException, RuntimeException {
+        if (senseCamera == null) {
+            this.stop();
+        }
+
+        this.mCamera = senseCamera;
+
+        if (this.mCamera != null) {
+            this.mStartRequested = true;
+            this.startIfReady();
+        }
+    }
+
+    /**
+     * stop.
+     */
+    public void stop() {
+        if (this.mCamera != null) {
+            this.mCamera.stop();
+        }
+    }
+
+    /**
+     * release.
+     */
+    public void release() {
+        if (this.mCamera != null) {
+            this.mCamera.release();
+            this.mCamera = null;
+        }
+    }
+
+    private void startIfReady() throws IOException, RuntimeException {
+        if (this.mStartRequested && this.mSurfaceAvailable) {
+            this.mCamera.start(this.mSurfaceView.getHolder());
+            this.requestLayout();
+            this.mStartRequested = false;
+        }
+    }
+
+    @Override
+    protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
+        if (this.mCamera != null) {
+            Size size = this.mCamera.getPreviewSize();
+            if (size != null) {
+                int width = size.getWidth();
+                int height = size.getHeight();
+
+                if (this.isPortraitMode()) {
+                    int tmp = width;
+                    //noinspection SuspiciousNameCombination
+                    width = height;
+                    height = tmp;
+                }
+
+                final int layoutWidth = right - left;
+                final int layoutHeight = bottom - top;
+
+                int childWidth;
+                int childHeight;
+
+                final float layoutAspectRatio = layoutWidth / (float) layoutHeight;
+                final float cameraPreviewAspectRatio = width / (float) height;
+
+                if (Float.compare(layoutAspectRatio, cameraPreviewAspectRatio) <= 0) {
+                    childWidth = (int) (layoutHeight * cameraPreviewAspectRatio);
+                    childHeight = layoutHeight;
+                } else {
+                    childWidth = layoutWidth;
+                    childHeight = (int) (layoutWidth / cameraPreviewAspectRatio);
+                }
+
+                for (int i = 0; i < this.getChildCount(); ++i) {
+                    this.getChildAt(i).layout(0, 0, childWidth, childHeight);
+                }
+
+                try {
+                    this.startIfReady();
+                } catch (Exception e) {
+                    ((Activity) SenseCameraPreview.this.mContext).setResult(ActivityUtils.RESULT_CODE_CAMERA_ERROR);
+                    ((Activity) SenseCameraPreview.this.mContext).finish();
+                }
+            }
+        }
+    }
+
+    private boolean isPortraitMode() {
+        final int orientation = this.mContext.getResources().getConfiguration().orientation;
+        return orientation != Configuration.ORIENTATION_LANDSCAPE && orientation == Configuration.ORIENTATION_PORTRAIT;
+    }
+
+    /**
+     * convert boundInfo.
+     *
+     * @param boundInfo boundInfo to convert.
+     * @return converted boundInfo.
+     */
+    public BoundInfo convertBoundInfoToPicture(final BoundInfo boundInfo) {
+        final int cameraRotationDegrees = this.mCamera.getRotationDegrees();
+        final int imageWidth = this.mCamera.getPreviewSize().getWidth();
+        final int imageHeight = this.mCamera.getPreviewSize().getHeight();
+
+        final float scale = getScaleToConvert();
+
+        BoundInfo scaledBoundInfo =
+                new BoundInfo((int) (boundInfo.getX() * scale + 0.5f), (int) (boundInfo.getY() * scale + 0.5f),
+                        (int) (boundInfo.getRadius() * scale + 0.5f));
+
+        BoundInfo rotateBoundInfo = scaledBoundInfo;
+        switch (cameraRotationDegrees) {
+            case 90:
+                rotateBoundInfo = new BoundInfo(scaledBoundInfo.getY(), imageHeight - scaledBoundInfo.getX(),
+                        scaledBoundInfo.getRadius());
+                break;
+            case 180:
+                rotateBoundInfo =
+                        new BoundInfo(imageWidth - scaledBoundInfo.getX(), imageHeight - scaledBoundInfo.getY(),
+                                scaledBoundInfo.getRadius());
+                break;
+            case 270:
+                rotateBoundInfo = new BoundInfo(imageWidth - scaledBoundInfo.getY(), scaledBoundInfo.getX(),
+                        scaledBoundInfo.getRadius());
+                break;
+            case 0:
+                rotateBoundInfo = scaledBoundInfo;
+                break;
+            default:
+                break;
+        }
+
+        BoundInfo mirrorBoundInfo = rotateBoundInfo;
+        if (this.mCamera.getCameraFacing() == SenseCamera.CAMERA_FACING_FRONT) {
+            switch (cameraRotationDegrees) {
+                case 90:
+                case 270:
+                    mirrorBoundInfo = new BoundInfo(rotateBoundInfo.getX(), imageHeight - rotateBoundInfo.getY(),
+                            rotateBoundInfo.getRadius());
+                    break;
+                case 0:
+                case 180:
+                    mirrorBoundInfo = new BoundInfo(imageWidth - rotateBoundInfo.getX(), rotateBoundInfo.getY(),
+                            rotateBoundInfo.getRadius());
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return mirrorBoundInfo;
+    }
+
+    /**
+     * convert viewRect.
+     *
+     * @param viewRect viewRect.
+     * @return converted Rect.
+     */
+    @SuppressWarnings("SuspiciousNameCombination")
+    public Rect convertViewRectToPicture(final Rect viewRect) {
+        final int cameraRotationDegrees = this.mCamera.getRotationDegrees();
+        final int imageWidth = this.mCamera.getPreviewSize().getWidth();
+        final int imageHeight = this.mCamera.getPreviewSize().getHeight();
+
+        final float scale = getScaleToConvert();
+
+        scaledRect = new Rect((int) (viewRect.left * scale + 0.5f), (int) (viewRect.top * scale + 0.5f),
+                (int) (viewRect.right * scale + 0.5f), (int) (viewRect.bottom * scale + 0.5f));
+
+        Rect rotateRect = new Rect(scaledRect);
+        switch (cameraRotationDegrees) {
+            case 90:
+                rotateRect = new Rect(scaledRect.top, imageHeight - scaledRect.right, scaledRect.bottom,
+                        imageHeight - scaledRect.left);
+                break;
+            case 180:
+                rotateRect = new Rect(imageWidth - scaledRect.right, imageHeight - scaledRect.bottom,
+                        imageWidth - scaledRect.left, imageHeight - scaledRect.top);
+                break;
+            case 270:
+                rotateRect = new Rect(imageWidth - scaledRect.bottom, scaledRect.left, imageWidth - scaledRect.top,
+                        scaledRect.right);
+                break;
+            case 0:
+            default:
+                break;
+        }
+
+        Rect resultRect = new Rect(rotateRect);
+        if (this.mCamera.getCameraFacing() == SenseCamera.CAMERA_FACING_FRONT) {
+            switch (cameraRotationDegrees) {
+                case 90:
+                case 270:
+                    resultRect = new Rect(rotateRect.left, imageHeight - rotateRect.bottom, rotateRect.right,
+                            imageHeight - rotateRect.top);
+                    break;
+                case 0:
+                case 180:
+                    resultRect = new Rect(imageWidth - rotateRect.right, rotateRect.top, imageWidth - rotateRect.left,
+                            rotateRect.bottom);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return resultRect;
+    }
+
+    /**
+     * Get scale ratio for convert.
+     *
+     * @return scale ratio.
+     */
+    public float getScaleToConvert() {
+        final int viewWidth = this.getWidth();
+        final int viewHeight = this.getHeight();
+        final int cameraRotationDegrees = this.mCamera.getRotationDegrees();
+        final int imageWidth = this.mCamera.getPreviewSize().getWidth();
+        final int imageHeight = this.mCamera.getPreviewSize().getHeight();
+
+        float widthRatio;
+        float heightRatio;
+
+        switch (cameraRotationDegrees) {
+            case 90:
+            case 270:
+                widthRatio = (float) imageHeight / viewWidth;
+                heightRatio = (float) imageWidth / viewHeight;
+                break;
+            case 0:
+            case 180:
+            default:
+                widthRatio = (float) imageWidth / viewWidth;
+                heightRatio = (float) imageHeight / viewHeight;
+                break;
+        }
+
+        return widthRatio < heightRatio ? widthRatio : heightRatio;
+    }
+
+    private class SurfaceCallback implements SurfaceHolder.Callback {
+        @Override
+        public void surfaceCreated(final SurfaceHolder surface) {
+            SenseCameraPreview.this.mSurfaceAvailable = true;
+            try {
+                SenseCameraPreview.this.startIfReady();
+            } catch (Exception e) {
+                ((Activity) SenseCameraPreview.this.mContext).setResult(ActivityUtils.RESULT_CODE_CAMERA_ERROR);
+                ((Activity) SenseCameraPreview.this.mContext).finish();
+            }
+        }
+
+        @Override
+        public void surfaceDestroyed(final SurfaceHolder surface) {
+            SenseCameraPreview.this.mSurfaceAvailable = false;
+        }
+
+        @Override
+        public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) {
+            // noting.
+        }
+    }
+}
+
+

+ 45 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/DensityUtil.java

@@ -0,0 +1,45 @@
+package com.turui.liveness.motion.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+/**
+ * Created on 2018/2/27.
+ *
+ * @author Qiang Lili
+ */
+
+public class DensityUtil {
+    /**
+     * Transform dip to px.
+     */
+    public static int dip2px(Context context, float dipValue) {
+        Resources r = context.getResources();
+        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, r.getDisplayMetrics());
+    }
+
+    /**
+     * Transform px to px.
+     */
+    public static int px2dip(Context context, int pxValue) {
+        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pxValue,
+                context.getResources().getDisplayMetrics());
+    }
+
+    /**
+     * Transform px to sp.
+     */
+    public static int px2sp(Context context, float pxValue) {
+        float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (pxValue / fontScale + 0.5f);
+    }
+
+    /**
+     * Transform sp to px.
+     */
+    public static int sp2px(Context context, float spValue) {
+        float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (spValue * fontScale + 0.5f);
+    }
+}

+ 86 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/MediaController.java

@@ -0,0 +1,86 @@
+package com.turui.liveness.motion.util;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import cn.gxeea.zk.R;
+import com.sensetime.senseid.sdk.liveness.interactive.NativeMotion;
+
+/**
+ * Created on 2016/11/01 10:45.
+ *
+ * @author Han Xu
+ */
+public final class MediaController {
+
+    private MediaPlayer mMediaPlayer = null;
+
+    private MediaController() {
+        // Do nothing.
+    }
+
+    public static MediaController getInstance() {
+        return InstanceHolder.INSTANCE;
+    }
+
+    /**
+     * 释放媒体播放器.
+     */
+    public void release() {
+        if (mMediaPlayer == null) {
+            return;
+        }
+        mMediaPlayer.setOnPreparedListener(null);
+        mMediaPlayer.stop();
+        mMediaPlayer.reset();
+        mMediaPlayer.release();
+        mMediaPlayer = null;
+    }
+
+    /**
+     * 播放声音.
+     */
+    public void playNotice(Context context, int motion) {
+        switch (motion) {
+            case NativeMotion.CV_LIVENESS_BLINK:
+                play(context, R.raw.common_notice_blink);
+                break;
+            case NativeMotion.CV_LIVENESS_MOUTH:
+                play(context, R.raw.common_notice_mouth);
+                break;
+            case NativeMotion.CV_LIVENESS_HEADNOD:
+                play(context, R.raw.common_notice_nod);
+                break;
+            case NativeMotion.CV_LIVENESS_HEADYAW:
+                play(context, R.raw.common_notice_yaw);
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void play(Context context, int soundId) {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.stop();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+
+        AudioManager audioManager =
+                (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
+        audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
+            @Override
+            public void onAudioFocusChange(int focusChange) {
+                // Do nothing.
+            }
+        }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+
+        mMediaPlayer = MediaPlayer.create(context, soundId);
+        mMediaPlayer.setLooping(true);
+        mMediaPlayer.start();
+    }
+
+    private static class InstanceHolder {
+        private static final MediaController INSTANCE = new MediaController();
+    }
+}

+ 32 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/NetworkUtil.java

@@ -0,0 +1,32 @@
+package com.turui.liveness.motion.util;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+/**
+ * Created by Qiang Lili on 2017/8/14.
+ */
+
+public final class NetworkUtil {
+
+    /**
+     * 判断网络是否连接.
+     *
+     * @param context 应用的环境变量
+     * @return 网络已连接返回true,否则返回false
+     */
+    @SuppressLint("MissingPermission")
+    public static boolean isNetworkConnected(Context context) {
+        if (context != null) {
+            ConnectivityManager connectivityManager =
+                    (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+            if (networkInfo != null) {
+                return networkInfo.isAvailable();
+            }
+        }
+        return false;
+    }
+}

+ 31 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/util/ToastUtil.java

@@ -0,0 +1,31 @@
+package com.turui.liveness.motion.util;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+import cn.gxeea.zk.R;
+
+
+/**
+ * Created on 16/07/2018.
+ *
+ * @author Qiang Lili
+ */
+public class ToastUtil {
+
+    /**
+     * Show error.
+     */
+    public static void show(final Context context, final String message) {
+        Toast toast = new Toast(context);
+        toast.setGravity(Gravity.CENTER, 0, 0);
+        View view = LayoutInflater.from(context).inflate(R.layout.common_interaction_toast_view, null);
+        ((TextView) view.findViewById(R.id.txt_toast_message)).setText(message);
+        toast.setView(view);
+        toast.setDuration(Toast.LENGTH_LONG);
+        toast.show();
+    }
+}

+ 117 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/AbstractOverlayView.java

@@ -0,0 +1,117 @@
+package com.turui.liveness.motion.view;
+
+import android.content.Context;
+import android.graphics.*;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.ResourcesCompat;
+import cn.gxeea.zk.R;
+import com.turui.liveness.motion.util.DensityUtil;
+/**
+ * Created on 28/07/2018.
+ *
+ * @author Qiang Lili
+ */
+public abstract class AbstractOverlayView extends View {
+    private Path mPath = new Path();
+
+    private Paint mPaint;
+
+    private Paint mBackgroundPaint;
+
+    private int mBackgroundColor = Color.BLACK;
+
+    public AbstractOverlayView(Context context) {
+        super(context);
+        this.init();
+    }
+
+    public AbstractOverlayView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        this.init();
+    }
+
+    public AbstractOverlayView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        this.init();
+    }
+
+    private void init() {
+        this.mPaint = new Paint();
+        this.mPaint.setStyle(Paint.Style.STROKE);
+        this.mPaint.setColor(ResourcesCompat.getColor(this.getResources(), R.color.common_interaction_light_gray,
+                this.getContext().getTheme()));
+        this.mPaint.setStrokeWidth(DensityUtil.dip2px(this.getContext(), 1));
+    }
+
+    private Paint getBackgroundPaint() {
+        if (this.mBackgroundPaint != null) {
+            return mBackgroundPaint;
+        }
+        this.mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        this.mBackgroundPaint.clearShadowLayer();
+        this.mBackgroundPaint.setStyle(Paint.Style.FILL);
+        this.mBackgroundPaint.setColor(this.mBackgroundColor);
+        return this.mBackgroundPaint;
+    }
+
+    protected abstract void buildPath(@NonNull final Path path, final int viewWidth, final int viewHeight);
+
+    public abstract Rect getMaskBounds();
+
+    /**
+     * Set background color.
+     *
+     * @param color color.
+     */
+    public void setBackgroundColor(@ColorInt final int color) {
+        this.mBackgroundColor = color;
+
+        this.invalidate();
+    }
+
+    /**
+     * Set mask path color.
+     *
+     * @param color color
+     */
+    public void setMaskPathColor(@ColorInt final int color) {
+        this.mPaint.setColor(color);
+
+        this.invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        canvas.save();
+
+        this.mPath.reset();
+
+        this.buildPath(this.mPath, this.getWidth(), this.getHeight());
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            canvas.clipOutPath(this.mPath);
+            canvas.drawColor(this.mBackgroundColor);
+            canvas.restore();
+            canvas.drawPath(this.mPath, this.mPaint);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            canvas.clipPath(this.mPath, Region.Op.DIFFERENCE);
+            canvas.drawColor(this.mBackgroundColor);
+            canvas.restore();
+            canvas.drawPath(this.mPath, this.mPaint);
+        } else {
+            final Path path = new Path();
+            path.addRect(new RectF(new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight())), Path.Direction.CW);
+            path.addPath(this.mPath);
+            canvas.drawPath(path, getBackgroundPaint());
+            canvas.drawPath(this.mPath, this.mPaint);
+            canvas.restore();
+        }
+    }
+}

+ 145 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/CircleTimeView.java

@@ -0,0 +1,145 @@
+package com.turui.liveness.motion.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.*;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+import android.view.View;
+import cn.gxeea.zk.R;
+
+
+public class CircleTimeView extends View implements ITimeViewBase {
+
+    private static final int DEFAULT_CIRCLE_WIDTH = 7;
+    private static final int DEFAULT_MAX_TIME = 10;
+    private static final int DEFAULT_TEXT_SIZE = 20;
+    private static final int DEFAULT_CIRCLE_COLOR = Color.argb(235, 74, 138, 255);
+    private static final int DEFAULT_TEXT_COLOR = Color.argb(255, 255, 255, 255);
+
+    private final float mDptopxScale = getResources().getDisplayMetrics().density;
+    private int mTime = DEFAULT_MAX_TIME;
+    private int mCircleColor;
+    private int mTextColor;
+
+    private float mCircleWidth = DEFAULT_CIRCLE_WIDTH;
+    private float mTextSize = DEFAULT_TEXT_SIZE;
+    private String mTimeInfo = "";
+
+    private Paint mTextPaint;
+    private Paint mRedusPaint;
+    private RectF mOver = new RectF();
+
+    public CircleTimeView(Context context) {
+        super(context);
+        init(null, 0);
+    }
+
+    public CircleTimeView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(attrs, 0);
+    }
+
+    public CircleTimeView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(attrs, defStyle);
+    }
+
+    private void init(AttributeSet attrs, int defStyle) {
+        final TypedArray attrArray =
+                getContext().obtainStyledAttributes(attrs, R.styleable.CircleTimeView, defStyle, 0);
+        initAttr(attrArray);
+        initPaint();
+        if (attrArray != null) {
+            attrArray.recycle();
+        }
+    }
+
+    private void initAttr(TypedArray attrArray) {
+        mCircleWidth =
+                (int) (attrArray.getInt(R.styleable.CircleTimeView_circle_width, DEFAULT_CIRCLE_WIDTH) * mDptopxScale);
+        mTime = (int) (attrArray.getInt(R.styleable.CircleTimeView_max_time, DEFAULT_MAX_TIME));
+        mTextSize = (int) (attrArray.getInt(R.styleable.CircleTimeView_text_size, DEFAULT_TEXT_SIZE) * mDptopxScale);
+        String tempColor = attrArray.getString(R.styleable.CircleTimeView_circle_color);
+        if (tempColor != null) {
+            try {
+                mCircleColor = Color.parseColor(tempColor);
+            } catch (IllegalArgumentException e) {
+                mCircleColor = DEFAULT_CIRCLE_COLOR;
+            }
+        }
+
+        tempColor = attrArray.getString(R.styleable.CircleTimeView_text_color);
+        if (tempColor != null) {
+            try {
+                mTextColor = Color.parseColor(tempColor);
+            } catch (IllegalArgumentException e) {
+                mTextColor = DEFAULT_TEXT_COLOR;
+            }
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        initOver();
+
+        canvas.drawCircle(this.getWidth() / 2f, this.getHeight() / 2, (float) (this.getWidth() / 2f), mRedusPaint);
+
+        Rect rect = new Rect();
+
+        mTextPaint.getTextBounds(mTimeInfo, 0, mTimeInfo.length(), rect);
+
+        canvas.drawText(mTimeInfo, this.getWidth() / 2f - rect.width() / 1.5f, this.getHeight() / 2 + rect.height() / 2,
+                mTextPaint);
+    }
+
+    private void initPaint() {
+
+        mRedusPaint = new Paint();
+        mRedusPaint.setAntiAlias(true);
+        mRedusPaint.setColor(mCircleColor);
+        mRedusPaint.setStrokeWidth(mCircleWidth);
+        mRedusPaint.setStyle(Style.FILL);
+
+        mTextPaint = new Paint();
+        mTextPaint.setAntiAlias(true);
+        mTextPaint.setColor(mTextColor);
+        mTextPaint.setStyle(Style.FILL);
+        mTextPaint.setTextSize(mTextSize);
+    }
+
+    private void initOver() {
+        mOver.top = mCircleWidth;
+        mOver.left = mCircleWidth;
+        mOver.bottom = this.getHeight() - mCircleWidth;
+        mOver.right = this.getWidth() - mCircleWidth;
+    }
+
+    public void setTime(int time) {
+        mTime = time;
+    }
+
+    /**
+     * 设置进度.
+     */
+    public void setProgress(float currentTime) {
+        mTimeInfo = String.valueOf((mTime - (int) currentTime));
+        invalidate();
+    }
+
+    @Override
+    public void hide() {
+        this.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void show() {
+        this.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public int getMaxTime() {
+        return mTime;
+    }
+}

+ 34 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/FixedSpeedScroller.java

@@ -0,0 +1,34 @@
+package com.turui.liveness.motion.view;
+
+import android.content.Context;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+public class FixedSpeedScroller extends Scroller {
+
+    private int mDuration = 1000;
+
+    public FixedSpeedScroller(Context context) {
+        super(context);
+    }
+
+    public FixedSpeedScroller(Context context, Interpolator interpolator) {
+        super(context, interpolator);
+    }
+
+    public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
+        super(context, interpolator, flywheel);
+    }
+
+    @Override
+    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+        // Ignore received duration, use fixed one instead
+        super.startScroll(startX, startY, dx, dy, mDuration);
+    }
+
+    @Override
+    public void startScroll(int startX, int startY, int dx, int dy) {
+        // Ignore received duration, use fixed one instead
+        super.startScroll(startX, startY, dx, dy, mDuration);
+    }
+}

+ 17 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/ITimeViewBase.java

@@ -0,0 +1,17 @@
+package com.turui.liveness.motion.view;
+
+/**
+ * Created on 2016/10/27.
+ *
+ * @author Han Xu
+ */
+public interface ITimeViewBase {
+
+    void setProgress(float currentTime);
+
+    void hide();
+
+    void show();
+
+    int getMaxTime();
+}

+ 44 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/InteractiveLivenessOverlayView.java

@@ -0,0 +1,44 @@
+package com.turui.liveness.motion.view;
+
+import android.content.Context;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Created on 2018/11/30.
+ *
+ * @author Zhu Xiangdong
+ */
+public class InteractiveLivenessOverlayView extends AbstractOverlayView {
+    private RectF mRectF = new RectF();
+
+    public InteractiveLivenessOverlayView(Context context) {
+        super(context);
+    }
+
+    public InteractiveLivenessOverlayView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public InteractiveLivenessOverlayView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void buildPath(@NonNull final Path path, final int viewWidth, final int viewHeight) {
+        this.mRectF.set(viewWidth * 0.1f, viewHeight * 0.1f, viewWidth * 0.9f, viewHeight * 0.9f);
+
+        path.addCircle(this.mRectF.centerX(), this.mRectF.centerY(), this.mRectF.width() / 2, Path.Direction.CCW);
+    }
+
+    @Override
+    public Rect getMaskBounds() {
+        final Rect rect = new Rect();
+        this.mRectF.round(rect);
+        return rect;
+    }
+}

+ 103 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/TimeViewContoller.java

@@ -0,0 +1,103 @@
+package com.turui.liveness.motion.view;
+
+import android.os.CountDownTimer;
+
+public class TimeViewContoller {
+
+    private ITimeViewBase mTimeView;
+    private CountDownTimer mCountDownTimer;
+    private float mCurrentTime;
+    private int mMaxTime;
+    private boolean mStop;
+    private CallBack mCallBack;
+
+    /**
+     * 构造方法.
+     */
+    public TimeViewContoller(ITimeViewBase view) {
+        mTimeView = view;
+        mMaxTime = mTimeView.getMaxTime();
+        mCountDownTimer = new CountDownTimer(mMaxTime * 1000, 50) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                mCurrentTime = mMaxTime - millisUntilFinished / 1000.0f;
+                mTimeView.setProgress(mCurrentTime);
+            }
+
+            @Override
+            public void onFinish() {
+                mTimeView.setProgress(mMaxTime);
+                onTimeEnd();
+            }
+        };
+    }
+
+    public void stop() {
+        mStop = true;
+        mCountDownTimer.cancel();
+    }
+
+    public void start() {
+        start(true);
+    }
+
+    /**
+     * 开始计时.
+     */
+    public void start(boolean again) {
+        if (!again) {
+            if (!mStop) {
+                return;
+            }
+            mStop = false;
+            if (mCurrentTime > mMaxTime) {
+                onTimeEnd();
+                return;
+            }
+            mCountDownTimer.cancel();
+            mCountDownTimer.start();
+        } else {
+            reset();
+        }
+    }
+
+    private void onTimeEnd() {
+        if (null != mCallBack) {
+            mCallBack.onTimeEnd();
+        }
+        if (!mStop) {
+            hide();
+        }
+    }
+
+    public void setCallBack(CallBack callback) {
+        mCallBack = callback;
+    }
+
+    private void reset() {
+        mCurrentTime = 0;
+        mTimeView.setProgress(mCurrentTime);
+        show();
+        mCountDownTimer.cancel();
+        mCountDownTimer.start();
+    }
+
+    /**
+     * 隐藏计时.
+     */
+    public void hide() {
+        mStop = true;
+        mCountDownTimer.cancel();
+        mTimeView.hide();
+    }
+
+    public void show() {
+        mStop = false;
+        mTimeView.show();
+    }
+
+    public interface CallBack {
+        void onTimeEnd();
+    }
+}

+ 244 - 0
ses-app/ses-app-android/app/src/main/java/com/turui/liveness/motion/view/WaterRippleView.java

@@ -0,0 +1,244 @@
+package com.turui.liveness.motion.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import cn.gxeea.zk.R;
+import com.turui.liveness.motion.util.DensityUtil;
+
+/**
+ * Created on 29/07/2018.
+ *
+ * @author Qiang Lili
+ */
+public class WaterRippleView extends View {
+
+    private static final int DEFAULT_RIPPLE_SPEED = 1;
+
+    private static final int DEFAULT_RIPPLE_COUNT = 1;
+
+    private static final int DEFAULT_RIPPLE_SPACE = 15;
+
+    private static final float DEFAULT_CENTER_IMAGE_RATIO = 1.0F;
+
+    private boolean mRunning = false;
+
+    private int[] mStrokeWidthArr;
+
+    private int mMaxStrokeWidth;
+
+    private int mRippleStrokeWidth = mMaxStrokeWidth;
+
+    private int mRippleCount = DEFAULT_RIPPLE_COUNT;
+
+    private int mRippleSpacing;
+
+    private int mWidth;
+
+    private int mHeight;
+
+    private Paint mPaint;
+
+    private Bitmap mBitmap;
+
+    private int mMaxBitmapWidth;
+
+    private int mMaxBitmapHeight;
+
+    private int mRippleSpeed = DEFAULT_RIPPLE_SPEED;
+
+    private float mImageRatio = DEFAULT_CENTER_IMAGE_RATIO;
+
+    public WaterRippleView(Context context) {
+        this(context, null);
+    }
+
+    public WaterRippleView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context context.
+     * @param attrs attrs.
+     * @param defStyleAttr defStyleAttr.
+     */
+    public WaterRippleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        initAttrs(context, attrs);
+    }
+
+    private void initAttrs(Context context, AttributeSet attrs) {
+
+        this.mMaxBitmapHeight = DensityUtil.dip2px(context, 31);
+
+        this.mMaxBitmapWidth = this.mMaxBitmapHeight;
+
+        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView);
+
+        final int waveColor = typedArray.getColor(R.styleable.WaterRippleView_rippleColor,
+                ContextCompat.getColor(context, R.color.common_interaction_ginger_yellow));
+
+        final Drawable drawable = typedArray.getDrawable(R.styleable.WaterRippleView_rippleCenterIcon);
+
+        this.mRippleCount = typedArray.getInt(R.styleable.WaterRippleView_rippleCount, DEFAULT_RIPPLE_COUNT);
+
+        this.mRippleSpacing = typedArray.getDimensionPixelSize(R.styleable.WaterRippleView_rippleSpacing,
+                DensityUtil.dip2px(context, DEFAULT_RIPPLE_SPACE));
+
+        this.mRippleSpeed = typedArray.getInt(R.styleable.WaterRippleView_rippleSpeed, DEFAULT_RIPPLE_SPEED);
+
+        this.mRunning = typedArray.getBoolean(R.styleable.WaterRippleView_rippleAutoRunning, false);
+
+        typedArray.recycle();
+
+        this.mBitmap = ((BitmapDrawable) drawable).getBitmap();
+
+        this.mPaint = new Paint();
+        this.mPaint.setAntiAlias(true);
+        this.mPaint.setStyle(Paint.Style.STROKE);
+        this.mPaint.setColor(waveColor);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int size = (this.mRippleCount * this.mRippleSpacing + this.mMaxBitmapWidth / 2) * 2;
+        if (this.mBitmap.getWidth() > this.mMaxBitmapWidth || this.mBitmap.getHeight() > this.mMaxBitmapHeight) {
+            size = (this.mRippleCount * this.mRippleSpacing + this.mBitmap.getWidth() / 2) * 2;
+        }
+
+        this.mWidth = resolveSize(size, widthMeasureSpec);
+        this.mHeight = resolveSize(size, heightMeasureSpec);
+
+        setMeasuredDimension(this.mWidth, this.mHeight);
+
+        this.mMaxStrokeWidth = (this.mWidth - this.mBitmap.getWidth()) / 2;
+
+        initArray();
+    }
+
+    private void initArray() {
+        this.mStrokeWidthArr = new int[this.mRippleCount];
+        for (int i = 0; i < this.mStrokeWidthArr.length; i++) {
+            this.mStrokeWidthArr[i] = -this.mRippleStrokeWidth / this.mRippleCount * i;
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        drawBitmap(canvas);
+
+        if (this.mRunning) {
+            drawRipple(canvas);
+            postInvalidateDelayed(2500 / this.mRippleSpeed);
+        }
+    }
+
+    private void drawBitmap(Canvas canvas) {
+        int left = (this.mWidth - this.mBitmap.getWidth()) / 2;
+        int top = (this.mHeight - this.mBitmap.getHeight()) / 2;
+
+        canvas.drawBitmap(this.mBitmap, left, top, null);
+    }
+
+    private void drawRipple(Canvas canvas) {
+        for (int strokeWidth : this.mStrokeWidthArr) {
+            if (strokeWidth < 0) {
+                continue;
+            }
+
+            this.mPaint.setStrokeWidth(strokeWidth);
+            this.mPaint.setAlpha(255 - 255 * strokeWidth / this.mRippleStrokeWidth);
+
+            canvas.drawCircle(this.mWidth / 2, this.mHeight / 2,
+                    (this.mBitmap.getWidth() * this.mImageRatio) / 2 + strokeWidth / 2, this.mPaint);
+        }
+        for (int i = 0; i < this.mStrokeWidthArr.length; i++) {
+
+            this.mStrokeWidthArr[i] += DensityUtil.dip2px(this.getContext(), 1);
+
+            if (this.mStrokeWidthArr[i] > this.mRippleStrokeWidth) {
+                this.mStrokeWidthArr[i] = 0;
+            }
+        }
+    }
+
+    /**
+     * Start wave ripple.
+     */
+    public void start() {
+        this.mRunning = true;
+        postInvalidate();
+    }
+
+    /**
+     * Stop wave ripple.
+     */
+    public void stop() {
+        this.mRunning = false;
+        initArray();
+        postInvalidate();
+    }
+
+    /**
+     * Set center bitmap to draw.
+     *
+     * @param bitmap bitmap.
+     */
+    public void setCenterBitmap(final Bitmap bitmap) {
+        if (bitmap.getHeight() > this.mBitmap.getHeight() || bitmap.getWidth() > this.mBitmap.getWidth()) {
+            return;
+        }
+        this.mBitmap = bitmap;
+        invalidate();
+    }
+
+    /**
+     * Set drawable resource for center icon.
+     *
+     * @param drawableResource drawable resource.
+     */
+    public void setCenterIconResource(final int drawableResource) {
+        this.mBitmap = ((BitmapDrawable) (ContextCompat.getDrawable(getContext(), drawableResource))).getBitmap();
+        invalidate();
+    }
+
+    /**
+     * Set stroke width of ripple.
+     *
+     * @param pixels pixels.
+     */
+    public void setRippleStrokeWidth(final int pixels) {
+        this.mRippleStrokeWidth = pixels > this.mMaxStrokeWidth ? this.mMaxStrokeWidth : pixels;
+    }
+
+    /**
+     * Set the speed of ripple, the bigger the value, the faster.
+     *
+     * @param speed speed.
+     */
+    public void setRippleSpeed(final int speed) {
+        this.mRippleSpeed = speed < 1 ? DEFAULT_RIPPLE_SPEED : speed;
+    }
+
+    /**
+     * Set the true scale of the image in the diagram, which determines the size of the ripples.
+     *
+     * @param ratio ratio.
+     */
+    public void setCenterImageRatio(final float ratio) {
+        this.mImageRatio = Float.compare(ratio, 0) > 0 ? ratio : DEFAULT_CENTER_IMAGE_RATIO;
+    }
+}

+ 11 - 0
ses-app/ses-app-android/app/src/main/res/anim/anim_rotate.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <rotate
+            android:duration="2000"
+            android:fromDegrees="0"
+            android:pivotX="50%"
+            android:pivotY="50%"
+            android:repeatCount="infinite"
+            android:toDegrees="360"
+            />
+</set>

+ 5 - 0
ses-app/ses-app-android/app/src/main/res/drawable-anydpi-v21/base_ic_baseline_photo_album_56_nor.xml

@@ -0,0 +1,5 @@
+<vector android:height="56dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="56dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#fff" android:pathData="M18,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12L6,4zM6,19l3,-3.86 2.14,2.58 3,-3.86L18,19L6,19z"/>
+</vector>

+ 5 - 0
ses-app/ses-app-android/app/src/main/res/drawable-anydpi-v21/base_ic_baseline_photo_album_56_press.xml

@@ -0,0 +1,5 @@
+<vector android:height="56dp" android:tint="#6200EE"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="56dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#fff" android:pathData="M18,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12L6,4zM6,19l3,-3.86 2.14,2.58 3,-3.86L18,19L6,19z"/>
+</vector>

BIN
ses-app/ses-app-android/app/src/main/res/drawable-hdpi-v4/base_ic_baseline_photo_album_56_nor.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-hdpi-v4/base_ic_baseline_photo_album_56_press.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_loading.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_motion_step_done.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_motion_step_ing.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-hdpi/common_ic_motion_step_wait.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-ldpi-v4/base_ic_baseline_photo_album_56_nor.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-ldpi-v4/base_ic_baseline_photo_album_56_press.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-mdpi-v4/base_ic_baseline_photo_album_56_nor.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-mdpi-v4/base_ic_baseline_photo_album_56_press.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xhdpi-v4/base_ic_baseline_photo_album_56_nor.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xhdpi-v4/base_ic_baseline_photo_album_56_press.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_loading.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_motion_step_done.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_motion_step_ing.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xhdpi/common_ic_motion_step_wait.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi-v4/base_ic_baseline_photo_album_56_nor.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi-v4/base_ic_baseline_photo_album_56_press.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi/common_ic_loading.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi/common_ic_motion_step_done.png


BIN
ses-app/ses-app-android/app/src/main/res/drawable-xxhdpi/common_ic_motion_step_ing.png


Some files were not shown because too many files changed in this diff