Prechádzať zdrojové kódy

refactor(更新)
- 人脸录入修改

周文健 4 mesiacov pred
rodič
commit
2d597943fe
18 zmenil súbory, kde vykonal 332 pridanie a 77 odobranie
  1. 1 0
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  2. 15 0
      app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt
  3. 4 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageFragment.kt
  4. 36 29
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/SopManageFragment.kt
  5. 4 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFaceFragment.kt
  6. 23 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopManageViewModel.kt
  7. 8 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt
  8. 1 0
      app/src/main/res/values-en/strings.xml
  9. 1 0
      app/src/main/res/values-zh/strings.xml
  10. 1 0
      app/src/main/res/values/strings.xml
  11. 14 4
      data/src/main/java/com/grkj/data/dao/JobTicketDao.kt
  12. 5 0
      data/src/main/java/com/grkj/data/repository/IJobTicketRepository.kt
  13. 5 0
      data/src/main/java/com/grkj/data/repository/IUserRepository.kt
  14. 4 0
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkJobTicketRepository.kt
  15. 4 0
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkUserRepository.kt
  16. 4 0
      data/src/main/java/com/grkj/data/repository/impl/standard/JobTicketRepository.kt
  17. 4 0
      data/src/main/java/com/grkj/data/repository/impl/standard/UserRepository.kt
  18. 198 42
      shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt

+ 1 - 0
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -142,6 +142,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
 
     override fun onResume() {
         super.onResume()
+        viewModel.registerFaceFeature().observe(this){}
         MainDomainData.clear()
         FingerprintUtil.init(this)
         FingerprintUtil.start()

+ 15 - 0
app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt

@@ -4,7 +4,9 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
 import com.grkj.data.repository.ISysMenuRepository
 import com.grkj.data.repository.IUserRepository
+import com.grkj.shared.utils.ArcSoftUtil
 import com.grkj.ui_base.base.BaseViewModel
+import com.sik.sikcore.extension.file
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
 import javax.inject.Inject
@@ -97,4 +99,17 @@ class LoginViewModel @Inject constructor(
             emit(logoutSuccess)
         }
     }
+
+    /**
+     * 注册人脸特征
+     */
+    fun registerFaceFeature(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val user = userRepository.getAllFaceData()
+            val userFaceData = user.filter { it.content.file().exists() }
+                .map { it.userId to it.content.file().readText() }
+            ArcSoftUtil.registerFace(userFaceData)
+            emit(true)
+        }
+    }
 }

+ 4 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageFragment.kt

@@ -66,6 +66,10 @@ class JobManageFragment : BaseFragment<FragmentJobManageBinding>() {
             PopTip.tip(R.string.please_select_job)
             return
         }
+        if (viewModel.jobManageDataList.any { it.ticketStatus !in listOf(JobTicketStatusEnum.CANCELED.status, JobTicketStatusEnum.FINISHED.status) }){
+            TipDialog.showError(getString(R.string.has_job_in_progress))
+            return
+        }
         TipDialog.show(
             msg = CommonUtils.getStr(R.string.check_delete_job).toString(),
             countDownTime = 10,

+ 36 - 29
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/SopManageFragment.kt

@@ -68,36 +68,43 @@ class SopManageFragment : BaseFragment<FragmentSopManageBinding>() {
             PopTip.tip(R.string.please_select_sop)
             return
         }
-        TipDialog.show(
-            msg = CommonUtils.getStr(R.string.check_delete_sop).toString(),
-            countDownTime = 10,
-            onConfirmClick = {
-                viewModel.deleteSelectedSop().observe(this) {
-                    if (it) {
-                        TipDialog.show(
-                            dialogType = TipDialog.DialogType.SUCCESS,
-                            msg = CommonUtils.getStr(R.string.sop_manage_delete_succeed)
-                                .toString(),
-                            showConfirm = false,
-                            countDownTime = 10,
-                            onConfirmClick = {
-                                getSopData(false)
-                            },
-                            onCancelClick = {
-                                getSopData(false)
+        viewModel.checkSopHasOnGoingJob().observe(this) {
+            if (it) {
+                TipDialog.showError(getString(R.string.has_job_in_progress))
+            } else {
+                TipDialog.show(
+                    msg = CommonUtils.getStr(R.string.check_delete_sop).toString(),
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        viewModel.deleteSelectedSop().observe(this) {
+                            if (it) {
+                                TipDialog.show(
+                                    dialogType = TipDialog.DialogType.SUCCESS,
+                                    msg = CommonUtils.getStr(R.string.sop_manage_delete_succeed)
+                                        .toString(),
+                                    showConfirm = false,
+                                    countDownTime = 10,
+                                    onConfirmClick = {
+                                        getSopData(false)
+                                    },
+                                    onCancelClick = {
+                                        getSopData(false)
+                                    }
+                                )
+                            } else {
+                                TipDialog.show(
+                                    dialogType = TipDialog.DialogType.ERROR,
+                                    msg = CommonUtils.getStr(R.string.sop_manage_delete_failed)
+                                        .toString(),
+                                    showConfirm = false,
+                                    countDownTime = 10
+                                )
                             }
-                        )
-                    } else {
-                        TipDialog.show(
-                            dialogType = TipDialog.DialogType.ERROR,
-                            msg = CommonUtils.getStr(R.string.sop_manage_delete_failed)
-                                .toString(),
-                            showConfirm = false,
-                            countDownTime = 10
-                        )
-                    }
-                }
-            })
+                        }
+                    })
+            }
+        }
+
     }
 
     private fun setSelectAllListener() {

+ 4 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFaceFragment.kt

@@ -76,15 +76,18 @@ class SetFaceFragment : BaseFragment<FragmentSetFaceBinding>() {
             binding.faceSetLayout.isVisible = false
             val saveFileName =
                 "${MainDomainData.userInfo?.userId}_face_${TimeUtils.nowString("yyyyMMddHHmmss")}"
+            val imageData =
+                ImageConvertUtils.bitmapToBase64(mCapturedBitmap).toString()
             FileStorageUtils.writeText(
                 CommonConstants.FACE_FOLDER,
                 saveFileName,
-                ImageConvertUtils.bitmapToBase64(mCapturedBitmap).toString()
+                imageData
             )
             val savePath = FileStorageUtils.getFilePath(
                 CommonConstants.FACE_FOLDER,
                 saveFileName
             )
+            viewModel.registerFaceFeature(imageData).observe(this) {}
             viewModel.saveUserFace(savePath).observe(this) {
                 getData()
             }

+ 23 - 1
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopManageViewModel.kt

@@ -2,7 +2,9 @@ package com.grkj.iscs.features.main.viewmodel.job_manage
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.grkj.data.enums.JobTicketStatusEnum
 import com.grkj.data.model.vo.SopManageVo
+import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.ISopRepository
 import com.grkj.ui_base.base.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
@@ -13,7 +15,10 @@ import kotlinx.coroutines.Dispatchers
  * SOP管理
  */
 @HiltViewModel
-class SopManageViewModel @Inject constructor(val sopRepository: ISopRepository) : BaseViewModel() {
+class SopManageViewModel @Inject constructor(
+    val sopRepository: ISopRepository,
+    val jobTicketRepository: IJobTicketRepository
+) : BaseViewModel() {
     var sopManageDataList: MutableList<SopManageVo> = mutableListOf()
     private var current: Int = 0
     private var size: Int = 50
@@ -44,4 +49,21 @@ class SopManageViewModel @Inject constructor(val sopRepository: ISopRepository)
             emit(true)
         }
     }
+
+    /**
+     * 检查是否有正在进行中的作业
+     */
+    fun checkSopHasOnGoingJob(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val jobTicketBySopIds =
+                jobTicketRepository.getJobTicketBySopIds(sopManageDataList.filter { it.isSelected }
+                    .map { it.sopId })
+            emit(jobTicketBySopIds.any {
+                it.ticketStatus !in listOf(
+                    JobTicketStatusEnum.CANCELED.status,
+                    JobTicketStatusEnum.FINISHED.status
+                )
+            })
+        }
+    }
 }

+ 8 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt

@@ -8,6 +8,7 @@ import com.grkj.data.model.dos.SysUserCharacteristicDo
 import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.repository.IHardwareRepository
 import com.grkj.data.repository.IUserRepository
+import com.grkj.shared.utils.ArcSoftUtil
 import com.grkj.shared.utils.BCryptUtils
 import com.grkj.ui_base.base.BaseViewModel
 import dagger.hilt.android.lifecycle.HiltViewModel
@@ -151,4 +152,11 @@ class UserInfoViewModel @Inject constructor(
             emit(true)
         }
     }
+
+    fun registerFaceFeature(imageData: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            ArcSoftUtil.registerFace(listOf((MainDomainData.userInfo?.userId ?: 0) to imageData))
+            emit(true)
+        }
+    }
 }

+ 1 - 0
app/src/main/res/values-en/strings.xml

@@ -453,5 +453,6 @@
     <string name="please_press_the_fingerprint_again">Please press the fingerprint again</string>
     <string name="fingerprint_register_failed">Fingerprint input failed</string>
     <string name="detect_face_tip">Detected face, about to shoot</string>
+    <string name="has_job_in_progress">There are ongoing assignments</string>
 
 </resources>

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -453,5 +453,6 @@
     <string name="please_press_the_fingerprint_again">请再次按压指纹</string>
     <string name="fingerprint_register_failed">指纹录入失败</string>
     <string name="detect_face_tip">检测到人脸,即将拍摄</string>
+    <string name="has_job_in_progress">存在正在进行中的作业</string>
 
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -456,5 +456,6 @@
     <string name="please_press_the_fingerprint_again">请再次按压指纹</string>
     <string name="fingerprint_register_failed">指纹录入失败</string>
     <string name="detect_face_tip">检测到人脸,即将拍摄</string>
+    <string name="has_job_in_progress">存在正在进行中的作业</string>
 
 </resources>

+ 14 - 4
data/src/main/java/com/grkj/data/dao/JobTicketDao.kt

@@ -507,7 +507,8 @@ interface JobTicketDao {
     /**
      * 获取锁定中的点位
      */
-    @Query("""
+    @Query(
+        """
         select count(1) from is_job_ticket_points ijtp 
         left join is_job_ticket ijt on ijtp.ticket_id = ijt.ticket_id
         where ijtp.point_status = "1" 
@@ -515,19 +516,28 @@ interface JobTicketDao {
         and (:selectedWorkflowMode IS NULL OR trim(:selectedWorkflowMode) = '' OR ijt.mode_id=:selectedWorkflowMode)
         and (:realTimeDataZoneId IS NULL OR trim(:realTimeDataZoneId) = '' OR ijt.workstation_id = :realTimeDataZoneId)
         group by ijtp.point_id
-    """)
+    """
+    )
     fun getLockedPointsCount(realTimeDataZoneId: Long?, selectedWorkflowMode: Long?): Int
 
     /**
      * 获取使用中的硬件数量
      */
-    @Query("""
+    @Query(
+        """
         select count(1) from is_job_ticket_points ijtp
         left join is_job_ticket ijt on ijtp.ticket_id = ijt.ticket_id
         where ijt.ticket_status in ('1','2','3','4','7')
         and (:workflowModeId IS NULL OR trim(:workflowModeId) = '' OR ijt.mode_id = :workflowModeId)
         and (:workstationId IS NULL OR trim(:workstationId) = '' OR ijt.workstation_id = :workstationId)
         group by ijtp.lock_id
-    """)
+    """
+    )
     fun getUsedHardwareCount(workstationId: Long?, workflowModeId: Long?): Int
+
+    /**
+     * 根据sopId获取作业
+     */
+    @Query("select * from is_job_ticket where sop_id in (:sopIds)")
+    fun getTicketBySopIds(sopIds: List<Long>): List<IsJobTicket>
 }

+ 5 - 0
data/src/main/java/com/grkj/data/repository/IJobTicketRepository.kt

@@ -274,4 +274,9 @@ interface IJobTicketRepository {
         workstationId: Long,
         jobName: String
     ): Long
+
+    /**
+     * 根据sopid获取作业
+     */
+    fun getJobTicketBySopIds(sopIds: List<Long>): List<IsJobTicket>
 }

+ 5 - 0
data/src/main/java/com/grkj/data/repository/IUserRepository.kt

@@ -143,4 +143,9 @@ interface IUserRepository {
      * 根据用户id获取用户生物数据
      */
     fun getUserBiometricDataByUserIds(userIds: List<Long>): List<SysBiometricDataVo>
+
+    /**
+     * 获取所有人脸数据
+     */
+    fun getAllFaceData(): List<SysUserCharacteristicDo>
 }

+ 4 - 0
data/src/main/java/com/grkj/data/repository/impl/network/NetworkJobTicketRepository.kt

@@ -264,4 +264,8 @@ class NetworkJobTicketRepository  @Inject constructor() : BaseRepository(), IJob
     ): Long {
         TODO("Not yet implemented")
     }
+
+    override fun getJobTicketBySopIds(sopIds: List<Long>): List<IsJobTicket> {
+        TODO("Not yet implemented")
+    }
 }

+ 4 - 0
data/src/main/java/com/grkj/data/repository/impl/network/NetworkUserRepository.kt

@@ -105,6 +105,10 @@ class NetworkUserRepository @Inject constructor() : BaseRepository(), IUserRepos
         TODO("Not yet implemented")
     }
 
+    override fun getAllFaceData(): List<SysUserCharacteristicDo> {
+        TODO("Not yet implemented")
+    }
+
     override fun getAllUsersWithRole(): List<SysUserVo> {
         TODO("Not yet implemented")
     }

+ 4 - 0
data/src/main/java/com/grkj/data/repository/impl/standard/JobTicketRepository.kt

@@ -116,6 +116,10 @@ class JobTicketRepository @Inject constructor(
         return ticketId
     }
 
+    override fun getJobTicketBySopIds(sopIds: List<Long>): List<IsJobTicket> {
+        return jobTicketDao.getTicketBySopIds(sopIds)
+    }
+
     override fun saveJob(
         selectedSopPoints: List<PointManageVo>,
         selectedLockerData: List<UserManageVo>,

+ 4 - 0
data/src/main/java/com/grkj/data/repository/impl/standard/UserRepository.kt

@@ -372,4 +372,8 @@ class UserRepository @Inject constructor(
     override fun getUserBiometricDataByUserIds(userIds: List<Long>): List<SysBiometricDataVo> {
         return userDao.getUserBiometricDataByUserIds(userIds)
     }
+
+    override fun getAllFaceData() : List<SysUserCharacteristicDo>{
+        return userDao.getFaceData()
+    }
 }

+ 198 - 42
shared/src/main/java/com/grkj/shared/utils/ArcSoftUtil.kt

@@ -16,6 +16,7 @@ import com.arcsoft.face.ErrorInfo
 import com.arcsoft.face.Face3DAngle
 import com.arcsoft.face.FaceEngine
 import com.arcsoft.face.FaceFeature
+import com.arcsoft.face.FaceFeatureInfo
 import com.arcsoft.face.FaceInfo
 import com.arcsoft.face.FaceSimilar
 import com.arcsoft.face.GenderInfo
@@ -24,13 +25,16 @@ import com.arcsoft.face.enums.DetectFaceOrientPriority
 import com.arcsoft.face.enums.DetectMode
 import com.arcsoft.face.enums.ExtractType
 import com.grkj.shared.config.Constants
-import com.grkj.shared.utils.extension.expandToPadCenter
 import com.grkj.shared.utils.extension.isInCenterArea
 import com.grkj.shared.utils.face.arcsoft.CameraHelper
 import com.grkj.shared.utils.face.arcsoft.CameraListener
 import com.grkj.shared.utils.face.arcsoft.NV21ToBitmap
 import com.grkj.shared.widget.FaceOverlayView
-import com.sik.sikimage.CropImageUtils
+import com.sik.sikimage.ImageConvertUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 
@@ -43,11 +47,12 @@ object ArcSoftUtil {
     private var previewSize: Camera.Size? = null
     private val rgbCameraId = Camera.CameraInfo.CAMERA_FACING_BACK
     private var faceEngine: FaceEngine? = null
+    private var checkFaceEngine: FaceEngine? = null
     private val cameraWidth: Int = 640
     private val cameraHeight: Int = 480
     private var afCode = -1
-    private val processMask: Int =
-        FaceEngine.ASF_AGE or FaceEngine.ASF_MASK_DETECT or FaceEngine.ASF_GENDER or FaceEngine.ASF_LIVENESS
+    private val processMask: Int = FaceEngine.ASF_MASK_DETECT or FaceEngine.ASF_LIVENESS
+    private val registerFaceFeatureJob get() = CoroutineScope(Dispatchers.IO + SupervisorJob())
 
     private const val ACTION_REQUEST_PERMISSIONS: Int = 0x001
     var isActivated = false
@@ -63,10 +68,7 @@ object ArcSoftUtil {
 
     fun checkActiveStatus(context: Context) {
         val activeCode = FaceEngine.activeOnline(
-            context,
-            Constants.ACTIVE_KEY,
-            Constants.APP_ID,
-            Constants.SDK_KEY
+            context, Constants.ACTIVE_KEY, Constants.APP_ID, Constants.SDK_KEY
         )
         when (activeCode) {
             ErrorInfo.MOK -> {
@@ -96,12 +98,24 @@ object ArcSoftUtil {
             return
         }
         faceEngine = FaceEngine()
+        checkFaceEngine = FaceEngine()
         afCode = faceEngine!!.init(
             context,
             DetectMode.ASF_DETECT_MODE_VIDEO,
             DetectFaceOrientPriority.ASF_OP_0_ONLY,
             1,
-            FaceEngine.ASF_FACE_DETECT or FaceEngine.ASF_AGE or FaceEngine.ASF_MASK_DETECT or FaceEngine.ASF_GENDER or FaceEngine.ASF_LIVENESS or FaceEngine.ASF_FACE_RECOGNITION
+            FaceEngine.ASF_FACE_DETECT or FaceEngine.ASF_MASK_DETECT or FaceEngine.ASF_LIVENESS or FaceEngine.ASF_FACE_RECOGNITION
+        )
+        logger.info("initEngine:  init: $afCode")
+        if (afCode != ErrorInfo.MOK) {
+            logger.info("初始化失败")
+        }
+        afCode = checkFaceEngine!!.init(
+            context,
+            DetectMode.ASF_DETECT_MODE_IMAGE,
+            DetectFaceOrientPriority.ASF_OP_0_ONLY,
+            1,
+            FaceEngine.ASF_FACE_DETECT or FaceEngine.ASF_MASK_DETECT or FaceEngine.ASF_LIVENESS or FaceEngine.ASF_FACE_RECOGNITION
         )
         logger.info("initEngine:  init: $afCode")
         isInit = afCode == ErrorInfo.MOK
@@ -131,10 +145,7 @@ object ArcSoftUtil {
 
         val cameraListener: CameraListener = object : CameraListener {
             override fun onCameraOpened(
-                camera: Camera,
-                cameraId: Int,
-                displayOrientation: Int,
-                isMirror: Boolean
+                camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean
             ) {
                 logger.info("onCameraOpened: $cameraId  $displayOrientation $isMirror")
                 previewSize = camera.parameters.previewSize
@@ -196,14 +207,11 @@ object ArcSoftUtil {
                     return
                 }
                 if (!needCheckCenter || (faceInfoList[0].rect.isInCenterArea(
-                        previewSize!!.width,
-                        previewSize!!.height
+                        previewSize!!.width, previewSize!!.height
                     ))
                 ) {
                     val bitmap = NV21ToBitmap(context).nv21ToBitmap(
-                        nv21,
-                        previewSize!!.width,
-                        previewSize!!.height
+                        nv21, previewSize!!.width, previewSize!!.height
                     )
 //                    val faceRect = faceInfoList[0].rect.expandToPadCenter()
                     logger.debug("人脸检测结果-识别结果 : ${bitmap == null} - $faceInfoList")
@@ -230,14 +238,160 @@ object ArcSoftUtil {
                 logger.info("onCameraConfigurationChanged: $cameraID  $displayOrientation")
             }
         }
-        cameraHelper = CameraHelper.Builder()
-            .previewViewSize(Point(cameraWidth, cameraHeight))
+        cameraHelper = CameraHelper.Builder().previewViewSize(Point(cameraWidth, cameraHeight))
+            .rotation(windowManager.defaultDisplay.rotation)
+            .specificCameraId(rgbCameraId ?: Camera.CameraInfo.CAMERA_FACING_FRONT).isMirror(false)
+            .previewOn(preview).cameraListener(cameraListener).build()
+        cameraHelper!!.init()
+        cameraHelper!!.start()
+    }
+
+    /**
+     * 注册人脸
+     */
+    fun registerFace(faceData: List<Pair<Long, String>>) {
+        faceData.forEachIndexed { index, userFace ->
+            registerFaceFeatureJob.launch {
+                val faceBitmap = decodeBase64ToBitmap(userFace.second)
+                val imageData =
+                    ImageConvertUtils.bitmapToNv21(faceBitmap, faceBitmap.width, faceBitmap.height)
+                val faceInfoList = mutableListOf<FaceInfo>()
+                checkFaceEngine?.detectFaces(
+                    imageData,
+                    faceBitmap.width,
+                    faceBitmap.height,
+                    FaceEngine.CP_PAF_NV21,
+                    faceInfoList
+                )
+                val faceFeature = FaceFeature()
+                checkFaceEngine?.extractFaceFeature(
+                    imageData,
+                    faceBitmap.width,
+                    faceBitmap.height,
+                    FaceEngine.CP_PAF_NV21,
+                    faceInfoList[0],
+                    ExtractType.REGISTER,
+                    0,
+                    faceFeature
+                )
+                val faceFeatureInfo =
+                    FaceFeatureInfo(userFace.first.toInt(), faceFeature.featureData)
+                if (checkFaceEngine?.getFaceFeature(userFace.first.toInt()) == null) {
+                    checkFaceEngine?.registerFaceFeature(faceFeatureInfo)
+                } else {
+                    checkFaceEngine?.updateFaceFeature(faceFeatureInfo)
+                }
+            }
+        }
+    }
+
+    @JvmOverloads
+    fun checkCamera(
+        context: Context,
+        windowManager: WindowManager,
+        preview: View,
+        needCheckCenter: Boolean = false,
+        callBack: (Long?) -> Unit
+    ) {
+        val metrics = DisplayMetrics()
+        windowManager.defaultDisplay.getMetrics(metrics)
+
+        val cameraListener: CameraListener = object : CameraListener {
+            override fun onCameraOpened(
+                camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean
+            ) {
+                logger.info("onCameraOpened: $cameraId  $displayOrientation $isMirror")
+                previewSize = camera.parameters.previewSize
+            }
+
+
+            override fun onPreview(nv21: ByteArray?, camera: Camera?) {
+                if (inDetecting) {
+                    return
+                }
+                inDetecting = true
+                val faceInfoList: List<FaceInfo> = ArrayList()
+                var code = checkFaceEngine!!.detectFaces(
+                    nv21,
+                    previewSize!!.width,
+                    previewSize!!.height,
+                    FaceEngine.CP_PAF_NV21,
+                    faceInfoList
+                )
+
+                if (needCheckCenter && !faceInfoList[0].rect.isInCenterArea(
+                        previewSize!!.width, previewSize!!.height
+                    )
+                ) {
+                    inDetecting = false
+                    return
+                }
+                if (code == ErrorInfo.MOK && faceInfoList.isNotEmpty()) {
+                    code = faceEngine!!.process(
+                        nv21,
+                        previewSize!!.width,
+                        previewSize!!.height,
+                        FaceEngine.CP_PAF_NV21,
+                        faceInfoList,
+                        processMask
+                    )
+                    if (code != ErrorInfo.MOK) {
+                        inDetecting = false
+                        return
+                    }
+                } else {
+                    inDetecting = false
+                    return
+                }
+
+                val faceLivenessInfoList: List<LivenessInfo> = ArrayList()
+                val livenessCode = faceEngine!!.getLiveness(faceLivenessInfoList)
+
+                // 有其中一个的错误码不为ErrorInfo.MOK,return
+                if ((livenessCode) != ErrorInfo.MOK) {
+                    logger.debug("人脸检测结果:年龄、性别、角度、获取验证失败")
+                    inDetecting = false
+                    return
+                }
+
+                // 自己加的,必须有活体检测
+                if (faceLivenessInfoList.none { it.liveness == LivenessInfo.ALIVE }) {
+                    callBack(null)
+                    inDetecting = false
+                    return
+                }
+                val faceFeature = FaceFeature()
+                checkFaceEngine?.extractFaceFeature(
+                    nv21,
+                    previewSize!!.width,
+                    previewSize!!.height,
+                    FaceEngine.CP_PAF_NV21,
+                    faceInfoList[0],
+                    ExtractType.RECOGNIZE,
+                    0,
+                    faceFeature
+                )
+                val searchResult = checkFaceEngine?.searchFaceFeature(faceFeature)
+                logger.debug("人脸检测结果-识别结果 : ${searchResult?.faceFeatureInfo} - $faceInfoList")
+                callBack(searchResult?.faceFeatureInfo?.searchId?.toLong())
+            }
+
+            override fun onCameraClosed() {
+                logger.info("onCameraClosed: ")
+            }
+
+            override fun onCameraError(e: Exception) {
+                logger.info("onCameraError: " + e.message)
+            }
+
+            override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
+                logger.info("onCameraConfigurationChanged: $cameraID  $displayOrientation")
+            }
+        }
+        cameraHelper = CameraHelper.Builder().previewViewSize(Point(cameraWidth, cameraHeight))
             .rotation(windowManager.defaultDisplay.rotation)
-            .specificCameraId(rgbCameraId ?: Camera.CameraInfo.CAMERA_FACING_FRONT)
-            .isMirror(false)
-            .previewOn(preview)
-            .cameraListener(cameraListener)
-            .build()
+            .specificCameraId(rgbCameraId ?: Camera.CameraInfo.CAMERA_FACING_FRONT).isMirror(false)
+            .previewOn(preview).cameraListener(cameraListener).build()
         cameraHelper!!.init()
         cameraHelper!!.start()
     }
@@ -265,9 +419,7 @@ object ArcSoftUtil {
      * @param threshold 阈值,一般设置 0.7f 左右
      */
     fun verifyFaceArcSoft(
-        b64a: String,
-        b64b: String,
-        threshold: Float = 0.7f
+        b64a: String, b64b: String, threshold: Float = 0.7f
     ): Boolean {
         // 1. 解码成 Bitmap
         val bmpA = decodeBase64ToBitmap(b64a)
@@ -280,18 +432,10 @@ object ArcSoftUtil {
         val imgB = bitmapToBgr24(bmpB)
 
         val imgADetectResultCode = faceEngine?.detectFaces(
-            imgA,
-            bmpA.width,
-            bmpA.height,
-            FaceEngine.CP_PAF_BGR24,
-            facesA
+            imgA, bmpA.width, bmpA.height, FaceEngine.CP_PAF_BGR24, facesA
         )
         val imgBDetectResultCode = faceEngine?.detectFaces(
-            imgB,
-            bmpB.width,
-            bmpB.height,
-            FaceEngine.CP_PAF_BGR24,
-            facesB
+            imgB, bmpB.width, bmpB.height, FaceEngine.CP_PAF_BGR24, facesB
         )
         logger.info("人脸检测结果1:${facesA.size},${facesA.size}")
         logger.info("人脸检测结果2:${imgADetectResultCode},${imgBDetectResultCode}")
@@ -303,12 +447,24 @@ object ArcSoftUtil {
         val ftA = FaceFeature()
         val ftB = FaceFeature()
         faceEngine?.extractFaceFeature(
-            imgA, bmpA.width, bmpA.height, FaceEngine.CP_PAF_BGR24,
-            facesA[0], ExtractType.RECOGNIZE, 0, ftA
+            imgA,
+            bmpA.width,
+            bmpA.height,
+            FaceEngine.CP_PAF_BGR24,
+            facesA[0],
+            ExtractType.RECOGNIZE,
+            0,
+            ftA
         )
         faceEngine?.extractFaceFeature(
-            imgB, bmpB.width, bmpB.height, FaceEngine.CP_PAF_BGR24,
-            facesB[0], ExtractType.RECOGNIZE, 0, ftB
+            imgB,
+            bmpB.width,
+            bmpB.height,
+            FaceEngine.CP_PAF_BGR24,
+            facesB[0],
+            ExtractType.RECOGNIZE,
+            0,
+            ftB
         )
 
         // 4. 特征比对