|
|
@@ -0,0 +1,648 @@
|
|
|
+package com.yingyang.login.ui.login
|
|
|
+
|
|
|
+import android.Manifest
|
|
|
+import android.content.Intent
|
|
|
+import android.content.pm.PackageManager
|
|
|
+import android.graphics.ImageFormat
|
|
|
+import android.graphics.Rect
|
|
|
+import android.graphics.YuvImage
|
|
|
+import android.os.Build
|
|
|
+import android.os.Bundle
|
|
|
+import android.os.Handler
|
|
|
+import android.os.Looper
|
|
|
+import android.text.TextUtils
|
|
|
+import android.util.Base64
|
|
|
+import android.util.Log
|
|
|
+import android.util.Size
|
|
|
+import android.view.View
|
|
|
+import android.widget.Button
|
|
|
+import android.widget.ImageView
|
|
|
+import android.widget.ProgressBar
|
|
|
+import android.widget.TextView
|
|
|
+import android.widget.Toast
|
|
|
+import androidx.activity.viewModels
|
|
|
+import androidx.appcompat.app.AppCompatActivity
|
|
|
+import androidx.camera.core.*
|
|
|
+import androidx.camera.lifecycle.ProcessCameraProvider
|
|
|
+import androidx.camera.view.PreviewView
|
|
|
+import androidx.core.app.ActivityCompat
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
+import com.alibaba.android.arouter.facade.annotation.Route
|
|
|
+import com.alibaba.android.arouter.launcher.ARouter
|
|
|
+import com.google.common.util.concurrent.ListenableFuture
|
|
|
+import com.google.mlkit.vision.common.InputImage
|
|
|
+import com.google.mlkit.vision.face.Face
|
|
|
+import com.google.mlkit.vision.face.FaceDetection
|
|
|
+import com.google.mlkit.vision.face.FaceDetectorOptions
|
|
|
+import com.yingyang.login.service.FaceOverlayView
|
|
|
+import java.io.ByteArrayOutputStream
|
|
|
+import java.util.concurrent.ExecutorService
|
|
|
+import java.util.concurrent.Executors
|
|
|
+import java.util.concurrent.TimeUnit
|
|
|
+import java.util.concurrent.atomic.AtomicInteger
|
|
|
+import com.yingyang.login.R
|
|
|
+import com.yingyang.login.service.InitService
|
|
|
+import com.yingyangfly.baselib.ext.toast
|
|
|
+import com.yingyangfly.baselib.router.RouterUrlCommon
|
|
|
+import com.yingyangfly.baselib.utils.JumpUtil
|
|
|
+import com.yingyangfly.baselib.utils.User
|
|
|
+
|
|
|
+@Route(path = RouterUrlCommon.humanFaceLogin)
|
|
|
+class FaceCaptureActivity : AppCompatActivity() {
|
|
|
+
|
|
|
+ // UI组件
|
|
|
+ private lateinit var previewView: PreviewView
|
|
|
+ private lateinit var startButton: Button
|
|
|
+ private lateinit var statusTextView: TextView
|
|
|
+ private lateinit var attemptsTextView: TextView
|
|
|
+ private lateinit var progressBar: ProgressBar
|
|
|
+ private lateinit var faceOverlayView: FaceOverlayView
|
|
|
+ private lateinit var iconBack: ImageView
|
|
|
+
|
|
|
+ // CameraX
|
|
|
+ private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
|
|
|
+ private var imageCapture: ImageCapture? = null
|
|
|
+ private var camera: Camera? = null
|
|
|
+ private lateinit var cameraExecutor: ExecutorService
|
|
|
+
|
|
|
+ // 人脸检测
|
|
|
+ private lateinit var faceDetector: com.google.mlkit.vision.face.FaceDetector
|
|
|
+
|
|
|
+ // 自动捕获状态
|
|
|
+ private var isAutoCaptureEnabled = false
|
|
|
+ private var isProcessingPhoto = false
|
|
|
+ private var captureAttempts = AtomicInteger(0)
|
|
|
+ private val maxCaptureAttempts = 3
|
|
|
+ private var currentAttempt = 1
|
|
|
+
|
|
|
+ // 人脸质量检测
|
|
|
+ private var stableFaceFrames = 0
|
|
|
+ private val requiredStableFrames = 5
|
|
|
+ private var lastFaceSize = 0f
|
|
|
+ private var lastFacePositionX = 0f
|
|
|
+ private var lastFacePositionY = 0f
|
|
|
+ private var lastDetectionTime = 0L
|
|
|
+ private val detectionCooldown = 1000L // 1秒冷却时间
|
|
|
+
|
|
|
+ // Handler for delayed tasks
|
|
|
+ private val mainHandler = Handler(Looper.getMainLooper())
|
|
|
+
|
|
|
+ // 声明 ViewModel
|
|
|
+ private val viewModel: LoginViewModel by viewModels()
|
|
|
+
|
|
|
+ private var token: String = ""
|
|
|
+
|
|
|
+ // 新增:防止重复请求的变量
|
|
|
+ private var isRequestInProgress = false
|
|
|
+ private var lastCaptureTime = 0L
|
|
|
+ private val minCaptureInterval = 2000L // 最小2秒间隔
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ private const val TAG = "FaceCapture"
|
|
|
+ private const val REQUEST_CODE_PERMISSIONS = 100
|
|
|
+ private const val FACE_SIZE_THRESHOLD = 0.2f
|
|
|
+ private const val FACE_MOVEMENT_THRESHOLD = 0.05f
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
+ super.onCreate(savedInstanceState)
|
|
|
+ setContentView(R.layout.login_activity_face_capture)
|
|
|
+
|
|
|
+ initViews()
|
|
|
+ initFaceDetector()
|
|
|
+
|
|
|
+ if (allPermissionsGranted()) {
|
|
|
+ startCamera()
|
|
|
+ } else {
|
|
|
+ requestPermissions()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun initViews() {
|
|
|
+ previewView = findViewById(R.id.preview_view)
|
|
|
+ startButton = findViewById(R.id.capture_button)
|
|
|
+ statusTextView = findViewById(R.id.status_text)
|
|
|
+ attemptsTextView = findViewById(R.id.attempts_text)
|
|
|
+ progressBar = findViewById(R.id.progress_bar)
|
|
|
+ faceOverlayView = findViewById(R.id.face_overlay)
|
|
|
+ iconBack = findViewById(R.id.icon_back)
|
|
|
+
|
|
|
+ cameraExecutor = Executors.newSingleThreadExecutor()
|
|
|
+
|
|
|
+ startButton.setOnClickListener {
|
|
|
+ if (!isAutoCaptureEnabled) {
|
|
|
+ startAutoCapture()
|
|
|
+ } else {
|
|
|
+ stopAutoCapture()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ iconBack.setOnClickListener {
|
|
|
+ JumpUtil.jumpActivity(RouterUrlCommon.login, this)
|
|
|
+ }
|
|
|
+
|
|
|
+ updateUIState()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun updateUIState() {
|
|
|
+ attemptsTextView.text = "尝试次数: $currentAttempt/$maxCaptureAttempts"
|
|
|
+
|
|
|
+ if (isAutoCaptureEnabled) {
|
|
|
+ startButton.text = "停止"
|
|
|
+ statusTextView.text = "正在检测人脸..."
|
|
|
+ } else {
|
|
|
+ startButton.text = "开始"
|
|
|
+ if (currentAttempt > 1) {
|
|
|
+ statusTextView.text = "等待重新开始..."
|
|
|
+ } else {
|
|
|
+ statusTextView.text = "准备就绪"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun initFaceDetector() {
|
|
|
+ val options = FaceDetectorOptions.Builder()
|
|
|
+ .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
|
|
|
+ .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
|
|
|
+ .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
|
|
|
+ .setMinFaceSize(0.15f)
|
|
|
+ .build()
|
|
|
+
|
|
|
+ faceDetector = FaceDetection.getClient(options)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun allPermissionsGranted(): Boolean {
|
|
|
+ val requiredPermissions = mutableListOf<String>().apply {
|
|
|
+ add(Manifest.permission.CAMERA)
|
|
|
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
|
|
+ add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return requiredPermissions.all {
|
|
|
+ ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun requestPermissions() {
|
|
|
+ val permissions = mutableListOf<String>().apply {
|
|
|
+ add(Manifest.permission.CAMERA)
|
|
|
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
|
|
+ add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ActivityCompat.requestPermissions(this, permissions.toTypedArray(), REQUEST_CODE_PERMISSIONS)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onRequestPermissionsResult(
|
|
|
+ requestCode: Int,
|
|
|
+ permissions: Array<out String>,
|
|
|
+ grantResults: IntArray
|
|
|
+ ) {
|
|
|
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
|
+ if (requestCode == REQUEST_CODE_PERMISSIONS) {
|
|
|
+ if (allPermissionsGranted()) {
|
|
|
+ startCamera()
|
|
|
+ } else {
|
|
|
+ Toast.makeText(this, "需要相机权限才能使用", Toast.LENGTH_SHORT).show()
|
|
|
+ finish()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun startCamera() {
|
|
|
+ cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
|
|
+
|
|
|
+ cameraProviderFuture.addListener(Runnable {
|
|
|
+ val cameraProvider = cameraProviderFuture.get()
|
|
|
+ val rotation = previewView.display.rotation
|
|
|
+
|
|
|
+ // 预览配置
|
|
|
+ val preview = Preview.Builder()
|
|
|
+ .setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
|
|
+ .setTargetRotation(rotation)
|
|
|
+ .build()
|
|
|
+ .also {
|
|
|
+ it.setSurfaceProvider(previewView.surfaceProvider)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图像捕获配置
|
|
|
+ imageCapture = ImageCapture.Builder()
|
|
|
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
|
+ .setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
|
|
+ .setTargetRotation(rotation)
|
|
|
+ .setFlashMode(ImageCapture.FLASH_MODE_OFF)
|
|
|
+ .build()
|
|
|
+
|
|
|
+ // 图像分析配置
|
|
|
+ val imageAnalyzer = ImageAnalysis.Builder()
|
|
|
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
|
+ .setTargetRotation(rotation)
|
|
|
+ .apply {
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
+ setTargetResolution(Size(640, 480))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .build()
|
|
|
+ .also { analyzer ->
|
|
|
+ analyzer.setAnalyzer(cameraExecutor, ImageAnalysis.Analyzer { imageProxy ->
|
|
|
+ analyzeImage(imageProxy)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择摄像头
|
|
|
+ val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
|
|
|
+
|
|
|
+ try {
|
|
|
+ cameraProvider.unbindAll()
|
|
|
+ camera = cameraProvider.bindToLifecycle(
|
|
|
+ this, cameraSelector, preview, imageCapture, imageAnalyzer
|
|
|
+ )
|
|
|
+ updateStatus("相机就绪")
|
|
|
+ } catch (e: Exception) {
|
|
|
+ Log.e(TAG, "相机启动失败: ${e.message}")
|
|
|
+ updateStatus("相机启动失败")
|
|
|
+ Toast.makeText(this, "相机启动失败", Toast.LENGTH_SHORT).show()
|
|
|
+ }
|
|
|
+ }, ContextCompat.getMainExecutor(this))
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun analyzeImage(imageProxy: ImageProxy) {
|
|
|
+ // 新增:添加时间间隔检查
|
|
|
+ val currentTime = System.currentTimeMillis()
|
|
|
+ if (currentTime - lastCaptureTime < minCaptureInterval) {
|
|
|
+ imageProxy.close()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isAutoCaptureEnabled || isProcessingPhoto) {
|
|
|
+ imageProxy.close()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ val mediaImage = imageProxy.image
|
|
|
+ if (mediaImage == null) {
|
|
|
+ imageProxy.close()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查冷却时间
|
|
|
+ if (currentTime - lastDetectionTime < detectionCooldown) {
|
|
|
+ imageProxy.close()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
|
|
+
|
|
|
+ faceDetector.process(image)
|
|
|
+ .addOnSuccessListener { faces ->
|
|
|
+ handleFaceDetectionResult(faces, imageProxy)
|
|
|
+ }
|
|
|
+ .addOnFailureListener { e ->
|
|
|
+ Log.e(TAG, "人脸检测失败: ${e.message}")
|
|
|
+ imageProxy.close()
|
|
|
+ }
|
|
|
+ .addOnCompleteListener {
|
|
|
+ // 确保 imageProxy 被关闭 - 直接调用 close() 是安全的
|
|
|
+ imageProxy.close()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun handleFaceDetectionResult(faces: List<Face>, imageProxy: ImageProxy) {
|
|
|
+ runOnUiThread {
|
|
|
+ faceOverlayView.setFaces(faces, imageProxy.width, imageProxy.height)
|
|
|
+
|
|
|
+ if (faces.isEmpty()) {
|
|
|
+ stableFaceFrames = 0
|
|
|
+ updateStatus("未检测到人脸")
|
|
|
+ imageProxy.close()
|
|
|
+ return@runOnUiThread
|
|
|
+ }
|
|
|
+
|
|
|
+ val face = faces.first()
|
|
|
+ val boundingBox = face.boundingBox
|
|
|
+
|
|
|
+ // 计算人脸位置和大小
|
|
|
+ val faceCenterX = (boundingBox.left + boundingBox.right) / 2f / imageProxy.width
|
|
|
+ val faceCenterY = (boundingBox.top + boundingBox.bottom) / 2f / imageProxy.height
|
|
|
+ val faceSize = boundingBox.width().toFloat() / imageProxy.width
|
|
|
+
|
|
|
+ // 检查人脸质量
|
|
|
+ val isFaceCentered = Math.abs(faceCenterX - 0.5f) < 0.1f && Math.abs(faceCenterY - 0.5f) < 0.1f
|
|
|
+ val isFaceGoodSize = faceSize > FACE_SIZE_THRESHOLD
|
|
|
+
|
|
|
+ if (isFaceCentered && isFaceGoodSize) {
|
|
|
+ // 检查人脸稳定性
|
|
|
+ if (stableFaceFrames == 0 ||
|
|
|
+ (Math.abs(faceSize - lastFaceSize) < FACE_MOVEMENT_THRESHOLD &&
|
|
|
+ Math.abs(faceCenterX - lastFacePositionX) < FACE_MOVEMENT_THRESHOLD &&
|
|
|
+ Math.abs(faceCenterY - lastFacePositionY) < FACE_MOVEMENT_THRESHOLD)) {
|
|
|
+
|
|
|
+ stableFaceFrames++
|
|
|
+ updateStatus("检测到人脸,保持姿势 ($stableFaceFrames/$requiredStableFrames)")
|
|
|
+
|
|
|
+ if (stableFaceFrames >= requiredStableFrames) {
|
|
|
+ // 人脸稳定,触发自动拍照
|
|
|
+ lastDetectionTime = System.currentTimeMillis()
|
|
|
+ autoCaptureFace(imageProxy)
|
|
|
+ return@runOnUiThread
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ stableFaceFrames = 1
|
|
|
+ updateStatus("检测到人脸,请保持稳定")
|
|
|
+ }
|
|
|
+
|
|
|
+ lastFaceSize = faceSize
|
|
|
+ lastFacePositionX = faceCenterX
|
|
|
+ lastFacePositionY = faceCenterY
|
|
|
+ } else {
|
|
|
+ stableFaceFrames = 0
|
|
|
+ if (!isFaceCentered) {
|
|
|
+ updateStatus("请将人脸对准中心")
|
|
|
+ } else if (!isFaceGoodSize) {
|
|
|
+ updateStatus("请靠近摄像头")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ imageProxy.close()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun startAutoCapture() {
|
|
|
+ isAutoCaptureEnabled = true
|
|
|
+ stableFaceFrames = 0
|
|
|
+ captureAttempts.set(0)
|
|
|
+ currentAttempt = 1
|
|
|
+ isRequestInProgress = false // 重置请求状态
|
|
|
+ updateUIState()
|
|
|
+ updateStatus("开始检测人脸...")
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun stopAutoCapture() {
|
|
|
+ isAutoCaptureEnabled = false
|
|
|
+ isRequestInProgress = false // 重置请求状态
|
|
|
+ updateUIState()
|
|
|
+ updateStatus("已停止")
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun autoCaptureFace(imageProxy: ImageProxy) {
|
|
|
+ // 新增:添加双重检查,确保不会同时处理多个请求
|
|
|
+ if (isProcessingPhoto || isRequestInProgress) {
|
|
|
+ Log.d(TAG, "已有请求在处理中,跳过本次拍照")
|
|
|
+ imageProxy.close()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ isProcessingPhoto = true
|
|
|
+ isRequestInProgress = true
|
|
|
+ updateStatus("正在拍照...")
|
|
|
+ showProgressBar(true)
|
|
|
+
|
|
|
+ // 重置人脸稳定计数器,防止连续拍照
|
|
|
+ stableFaceFrames = 0
|
|
|
+ lastDetectionTime = System.currentTimeMillis()
|
|
|
+ lastCaptureTime = System.currentTimeMillis()
|
|
|
+
|
|
|
+ // 从 ImageProxy 获取图片并转换为 Base64
|
|
|
+ val base64Image = convertImageProxyToBase64(imageProxy)
|
|
|
+ imageProxy.close()
|
|
|
+
|
|
|
+ if (base64Image.isNotEmpty()) {
|
|
|
+ // 增加冷却时间,防止短时间内多次发送
|
|
|
+ mainHandler.postDelayed({
|
|
|
+ sendToServer(base64Image)
|
|
|
+ }, 500) // 添加500ms延迟
|
|
|
+ } else {
|
|
|
+ handleCaptureError("图片转换失败")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun convertImageProxyToBase64(imageProxy: ImageProxy): String {
|
|
|
+ val mediaImage = imageProxy.image ?: return ""
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 将 YUV_420_888 转换为 JPEG
|
|
|
+ val yBuffer = mediaImage.planes[0].buffer
|
|
|
+ val uBuffer = mediaImage.planes[1].buffer
|
|
|
+ val vBuffer = mediaImage.planes[2].buffer
|
|
|
+
|
|
|
+ val ySize = yBuffer.remaining()
|
|
|
+ val uSize = uBuffer.remaining()
|
|
|
+ val vSize = vBuffer.remaining()
|
|
|
+
|
|
|
+ val nv21 = ByteArray(ySize + uSize + vSize)
|
|
|
+
|
|
|
+ // Y plane
|
|
|
+ yBuffer.get(nv21, 0, ySize)
|
|
|
+ // V plane
|
|
|
+ vBuffer.get(nv21, ySize, vSize)
|
|
|
+ // U plane
|
|
|
+ uBuffer.get(nv21, ySize + vSize, uSize)
|
|
|
+
|
|
|
+ val yuvImage = YuvImage(nv21, ImageFormat.NV21, mediaImage.width, mediaImage.height, null)
|
|
|
+ val outputStream = ByteArrayOutputStream()
|
|
|
+ yuvImage.compressToJpeg(Rect(0, 0, mediaImage.width, mediaImage.height), 80, outputStream)
|
|
|
+
|
|
|
+ val jpegBytes = outputStream.toByteArray()
|
|
|
+ return Base64.encodeToString(jpegBytes, Base64.NO_WRAP)
|
|
|
+
|
|
|
+ } catch (e: Exception) {
|
|
|
+ Log.e(TAG, "图片转换失败: ${e.message}")
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun sendToServer(base64Image: String) {
|
|
|
+ updateStatus("正在验证...")
|
|
|
+
|
|
|
+ // 再次检查请求状态,避免重复请求
|
|
|
+ if (isRequestInProgress.not() || isProcessingPhoto.not()) {
|
|
|
+ Log.w(TAG, "请求状态异常,跳过本次请求")
|
|
|
+ isProcessingPhoto = false
|
|
|
+ isRequestInProgress = false
|
|
|
+ showProgressBar(false)
|
|
|
+ updateStatus("状态异常,请重新开始")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ viewModel.humanFaceLogin(
|
|
|
+ base64Image,
|
|
|
+ fail = { errorMessage ->
|
|
|
+ runOnUiThread {
|
|
|
+ handleServerResponse(
|
|
|
+ success = false,
|
|
|
+ errorMessage = errorMessage,
|
|
|
+ base64Image = base64Image
|
|
|
+ )
|
|
|
+ }
|
|
|
+ },
|
|
|
+ success = { token ->
|
|
|
+ runOnUiThread {
|
|
|
+ if (TextUtils.isEmpty(token).not()) {
|
|
|
+ this.token = token!!
|
|
|
+ User.saveToken(token)
|
|
|
+ handleServerResponse(
|
|
|
+ success = true,
|
|
|
+ errorMessage = null,
|
|
|
+ base64Image = base64Image
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ handleServerResponse(
|
|
|
+ success = false,
|
|
|
+ errorMessage = "登录失败",
|
|
|
+ base64Image = base64Image
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun handleServerResponse(success: Boolean, errorMessage: String?, base64Image: String) {
|
|
|
+ isProcessingPhoto = false
|
|
|
+ isRequestInProgress = false
|
|
|
+ showProgressBar(false)
|
|
|
+
|
|
|
+ if (success && TextUtils.isEmpty(token).not()) {
|
|
|
+ // 成功
|
|
|
+ updateStatus("人脸验证成功!")
|
|
|
+ Toast.makeText(this, "人脸验证成功", Toast.LENGTH_SHORT).show()
|
|
|
+
|
|
|
+ // 立即停止自动捕获
|
|
|
+ isAutoCaptureEnabled = false
|
|
|
+ updateUIState()
|
|
|
+
|
|
|
+ // 获取用户信息
|
|
|
+ getUserInfo()
|
|
|
+ } else {
|
|
|
+ // 失败
|
|
|
+ val attempts = captureAttempts.incrementAndGet()
|
|
|
+ val errorMsg = errorMessage ?: "验证失败"
|
|
|
+
|
|
|
+ if (attempts >= maxCaptureAttempts) {
|
|
|
+ // 达到最大尝试次数
|
|
|
+ updateStatus("验证失败,已达最大尝试次数")
|
|
|
+ Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
|
|
|
+
|
|
|
+ // 3秒后返回登录页面
|
|
|
+ mainHandler.postDelayed({
|
|
|
+ JumpUtil.jumpActivity(RouterUrlCommon.login, this)
|
|
|
+ finish()
|
|
|
+ }, 3000)
|
|
|
+ } else {
|
|
|
+ // 还有机会,准备重新尝试
|
|
|
+ currentAttempt++
|
|
|
+ updateUIState()
|
|
|
+ updateStatus("验证失败,准备第${currentAttempt}次尝试 ($errorMsg)")
|
|
|
+
|
|
|
+ // 添加冷却时间后重新开始
|
|
|
+ mainHandler.postDelayed({
|
|
|
+ if (isAutoCaptureEnabled && !isProcessingPhoto && !isRequestInProgress) {
|
|
|
+ stableFaceFrames = 0
|
|
|
+ updateStatus("开始第${currentAttempt}次尝试...")
|
|
|
+ // 延长冷却时间,避免连续失败
|
|
|
+ lastDetectionTime = System.currentTimeMillis() + 2000
|
|
|
+ }
|
|
|
+ }, 3000)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun handleCaptureError(errorMessage: String) {
|
|
|
+ isProcessingPhoto = false
|
|
|
+ isRequestInProgress = false
|
|
|
+ showProgressBar(false)
|
|
|
+
|
|
|
+ updateStatus("拍照失败: $errorMessage")
|
|
|
+ Toast.makeText(this, "拍照失败: $errorMessage", Toast.LENGTH_SHORT).show()
|
|
|
+
|
|
|
+ // 3秒后重试
|
|
|
+ mainHandler.postDelayed({
|
|
|
+ if (isAutoCaptureEnabled && !isProcessingPhoto && !isRequestInProgress) {
|
|
|
+ stableFaceFrames = 0
|
|
|
+ updateStatus("准备重新检测...")
|
|
|
+ }
|
|
|
+ }, 3000)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun updateStatus(message: String) {
|
|
|
+ runOnUiThread {
|
|
|
+ statusTextView.text = message
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun showProgressBar(show: Boolean) {
|
|
|
+ runOnUiThread {
|
|
|
+ progressBar.visibility = if (show) View.VISIBLE else View.GONE
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDestroy() {
|
|
|
+ super.onDestroy()
|
|
|
+ isAutoCaptureEnabled = false
|
|
|
+ isProcessingPhoto = false
|
|
|
+ isRequestInProgress = false
|
|
|
+ cameraExecutor.shutdown()
|
|
|
+ try {
|
|
|
+ if (!cameraExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
|
|
|
+ cameraExecutor.shutdownNow()
|
|
|
+ }
|
|
|
+ } catch (e: InterruptedException) {
|
|
|
+ cameraExecutor.shutdownNow()
|
|
|
+ }
|
|
|
+ faceDetector.close()
|
|
|
+ mainHandler.removeCallbacksAndMessages(null)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private fun getUserInfo() {
|
|
|
+ viewModel.getUserInfo(fail = {
|
|
|
+ it.toast()
|
|
|
+ }, success = {
|
|
|
+ runOnUiThread {
|
|
|
+ if (it != null) {
|
|
|
+ if (TextUtils.isEmpty(it.mobile).not()) {
|
|
|
+ User.saveMobile(it.mobile)
|
|
|
+ }
|
|
|
+ if (TextUtils.isEmpty(it.idCard).not()) {
|
|
|
+ User.saveIdCard(it.idCard)
|
|
|
+ }
|
|
|
+ //保存用户头像
|
|
|
+ if (TextUtils.isEmpty(it.avatar).not()) {
|
|
|
+ User.saveAvatar(it.avatar)
|
|
|
+ }
|
|
|
+ //保存用户名
|
|
|
+ if (TextUtils.isEmpty(it.name).not()) {
|
|
|
+ User.saveName(it.name)
|
|
|
+ }
|
|
|
+ User.saveUserSex(it.getSex())
|
|
|
+ User.saveUserAge(it.getAgeInfo())
|
|
|
+ if (TextUtils.isEmpty(it.orgCode).not()) {
|
|
|
+ User.saveOrgCode(it.orgCode)
|
|
|
+ }
|
|
|
+ if (TextUtils.isEmpty(it.id).not()) {
|
|
|
+ User.saveUserId(it.id)
|
|
|
+ }
|
|
|
+ if (TextUtils.isEmpty(it.padNo).not()) {
|
|
|
+ User.savePadNo(it.padNo)
|
|
|
+ }
|
|
|
+ //保存是否第一次登陆(0第一次登陆)
|
|
|
+ if (TextUtils.isEmpty(it.firstLogin).not()) {
|
|
|
+ User.saveFirstLogin(it.firstLogin)
|
|
|
+ }
|
|
|
+ startInitService()
|
|
|
+ ARouter.getInstance().build(RouterUrlCommon.home)
|
|
|
+ .withTransition(R.anim.leftin, R.anim.leftout).navigation(this)
|
|
|
+ finish()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun startInitService() {
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
+ startForegroundService(Intent(this, InitService::class.java))
|
|
|
+ } else {
|
|
|
+ startService(Intent(this, InitService::class.java))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|