package com.sw.platecabinet.activity import android.Manifest import android.content.Intent import android.graphics.Bitmap import android.graphics.Point import android.hardware.Camera import android.os.Build import android.os.CountDownTimer import android.util.DisplayMetrics import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import androidx.annotation.RequiresApi import androidx.lifecycle.Observer import com.arcsoft.face.ErrorInfo import com.google.gson.Gson import com.sw.inbound.utils.DateTimeUtils import com.sw.plate.utils.Base64 import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.ConfigUtil import com.sw.plate.utils.arcface.ErrorCodeUtil import com.sw.plate.utils.arcface.FaceRectTransformer import com.sw.plate.utils.arcface.FaceRectView import com.sw.plate.utils.arcface.FaceRectView.DrawInfo import com.sw.plate.utils.arcface.PreviewConfig import com.sw.plate.utils.arcface.camera.CameraListener import com.sw.plate.utils.arcface.camera.DualCameraHelper import com.sw.plate.utils.arcface.face.constants.LivenessType import com.sw.plate.utils.arcface.face.model.CompareResult import com.sw.plate.utils.arcface.face.model.FacePreviewInfo import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration import com.sw.plate.utils.arcface.facedb.FaceDatabase import com.sw.plate.utils.arcface.facedb.entity.FaceEntity import com.sw.plate.utils.arcface.model.UserFaceInfo import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY import com.sw.platecabinet.MyApp import com.sw.platecabinet.R import com.sw.platecabinet.databinding.ActivityLoginFaceBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding import com.sw.platecabinet.ext.clickWithDebounce import com.sw.platecabinet.ext.gone import com.sw.platecabinet.ext.visible import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.utils.BitmapSaver import com.sw.platecabinet.utils.Debouncer import com.sw.platecabinet.utils.LogFileUtil import com.sw.platecabinet.utils.PermissionHelper import com.sw.platecabinet.utils.PlateUtils import com.sw.platecabinet.utils.RegisterCallbackHandler import com.sw.platecabinet.utils.SoundPoolUtil import com.sw.platecabinet.utils.SpTool import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import timber.log.Timber import java.io.File import java.util.concurrent.atomic.AtomicBoolean /** * 人脸识别 */ class LoginByFaceActivity : BaseActivity(), ViewTreeObserver.OnGlobalLayoutListener { // private val recognizeViewModel by viewModels() private var countDownTimer: CountDownTimer? = null private val CAMERA_PERMISSION_REQUEST_CODE = 100 private val REQUIRED_PERMISSIONS: Array = arrayOf( Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE ) override fun inflateViewBinding(): ActivityLoginFaceBinding { return ActivityLoginFaceBinding.inflate(layoutInflater) } override fun inflateTitleBinding(): ItemTitleTimeBinding { return binding.includeHeader } companion object { private const val TAG = "LoginByFaceActivity" private var instance: LoginByFaceActivity? = null private const val COLLECT_SUCCESS = "collect_success" private const val RECOGNIZE_SUCCESS = "recognize_success" private const val NO_PLATE_REMIND = "no_plate_remind"; fun goInitActivity() { instance?.goInitActivity() } } // private var logList: MutableList = mutableListOf() // private val logAdapter by lazy { LogAdapter(logList) } override fun initialize() { instance = this userViewModel.activeEngine() // addSocketListener() initCountTime() initArcViewModel() initArcView() openRectInfoDraw = true recognizeViewModel.setDrawRectInfoTextValue(true) //viewModel.generateToken() // binding.llToPwd.setOnClickListener { // val intent = Intent(this, LoginByPwdActivity::class.java) // startActivity(intent) // } // TaskManager.startTask() EventBus.getDefault().register(this) // binding.btnCollectFace.setOnClickListener { collectFace() } // binding.rvLogInfo.let { // it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) // it.adapter = logAdapter // } // // binding.btnLogState.let { // it.tag = false // it.setImageResource(R.drawable.ic_log_show) // it.setOnClickListener { v -> // binding.rvLogInfo.run { // val state = it.tag.toString().toBooleanStrictOrNull() ?: false // if (state) visible() else gone() // } // val state = it.tag.toString().toBooleanStrictOrNull() ?: false // it.tag = state // it.setImageResource( // if (state) R.drawable.ic_log_hide else R.drawable.ic_log_show // ) // } // } //开启人脸增量数据定时任务 startFaceTask { loadLogInfo("collectFace增量接口数据用户id:${Gson().toJson(it)}") } val soundMap = hashMapOf() soundMap[COLLECT_SUCCESS] = R.raw.collect_success soundMap[RECOGNIZE_SUCCESS] = R.raw.recognize_success soundMap[NO_PLATE_REMIND] = R.raw.no_plate_remind SoundPoolUtil.getInstance().loadR(this, soundMap) binding.btnCollect.clickWithDebounce { //先重置,后采集 resetRecognizeState() //采集数据按钮 retryFailCount = 0 isCollectFace = true collectFace() } binding.btnRetry.clickWithDebounce { //重新识别按钮 resetRecognizeState() } } private fun checkCameraPermission() { Timber.i("checkCameraPermission") val permissionHelper = PermissionHelper.with(this, REQUIRED_PERMISSIONS, CAMERA_PERMISSION_REQUEST_CODE) .onGranted { loadLogInfo("checkCameraPermission 权限已允许") // 权限已授予 recognizeViewModel.init() initRgbCamera() resumeCamera() } .build() registerPermissionHelper(permissionHelper) permissionHelper.checkAndRequest() } @Subscribe(threadMode = ThreadMode.MAIN) fun getFaceEntity(insertEntity: FaceEntity) { Timber.tag("performSync").e("-time=%s", insertEntity.registerTime) recognizeViewModel.addFace(insertEntity) Timber.tag("performSync over").e("-time=%s", insertEntity.registerTime) } override fun onLeftDoubleClick() { finish() } private var isRecognition = false private var rgbCameraHelper: DualCameraHelper? = null private var rgbFaceRectTransformer: FaceRectTransformer? = null private val livenessType = LivenessType.RGB private var openRectInfoDraw = false private val regDebouncer = Debouncer(3000) private var registerHandler: RegisterCallbackHandler? = null private fun isFitData(userFaceInfo: UserFaceInfo?): Boolean { if (userFaceInfo == null) return false val headBmp = userFaceInfo.headBmp if (headBmp == null) return false // if (headBmp.width <= 200 || headBmp.height <= 200) { // loadLogInfo("collectFace,isFitData:拿到人脸提取特征数据照片过小-----------") // return false // } return true } private fun initArcViewModel() { recognizeViewModel.setLiveType(livenessType) recognizeViewModel.prepareRegister() recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo -> registerHandler?.setCallback { if (!isCollectFace) { return@setCallback } if (this.userFaceInfo == null && isFitData(userFaceInfo)) { val headBmp = userFaceInfo.headBmp if (headBmp.width <= 200 || headBmp.height <= 200) { loadLogInfo("collectFace,isFitData:拿到人脸提取特征数据照片过小w:${headBmp.width},h:${headBmp.height}-----------") binding.llCollectButton.visible() binding.btnRetry.gone() binding.btnCollect.text = "重新采集" return@setCallback } recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE) this.userFaceInfo = userFaceInfo getFaceDataBlock() } } } recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? -> if (ftInitCode != ErrorInfo.MOK) { val error: String? = getString( R.string.specific_engine_init_failed, "ftEngine", ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) ) Timber.e("ftInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.frInitCode.observe(this, Observer { frInitCode: Int? -> if (frInitCode != ErrorInfo.MOK) { val error: String? = getString( R.string.specific_engine_init_failed, "frEngine", frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) ) Timber.e("frInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.flInitCode.observe(this, Observer { flInitCode: Int? -> if (flInitCode != ErrorInfo.MOK) { val error: String? = getString( R.string.specific_engine_init_failed, "flEngine", flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) ) Timber.e("flInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.recognizeConfiguration .observe(this, Observer { recognizeConfiguration: RecognizeConfiguration? -> Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") }) recognizeViewModel.recognizeNotice.observe(this, Observer { notice: String? -> Timber.i("recognizeNotice observe notice = $notice") }) recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? -> loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}") loadFaceRecognizeResult3(result) }) recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> Timber.i("drawRectInfoText observe info = $info") }) } /** * 人脸识别开关,true-loadFaceRecognizeResult2,false-loadFaceRecognizeResult */ private var faceUseSwitch = true /** * 识别失败重试次数 */ private var retryFailCount = 0 /** * 识别失败最大次数 */ private val maxFailCount = 5 /** * 人脸失败阈值 */ private val faceFailThreshold = 0.3 /** * 配置阈值,默认值为0.8 */ private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) } private val openPlateDebouncer = Debouncer(5000) private val isCollecting = AtomicBoolean(false) private fun loadFaceRecognizeResult3(result: CompareResult?) { val similar = result?.similar ?: return binding.tvSimilarValue.text = "$similar,${retryFailCount}次" loadLogInfo( "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " + "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount" ) if (similar >= faceSuccessThreshold) { val userId = result.faceEntity?.userName if (userId.isNullOrBlank()) { loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略") return } loadLogInfo( "collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}" ) // 成功后必须重置状态 retryFailCount = 0 isCollecting.set(false) isCollectFace = false currentUserId = userId // 防抖,防止重复开闸 openPlateDebouncer.debounce { openPlate(userId) } return } if (isCollectFace) { loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar") return } // if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) { if (retryFailCount >= maxFailCount) { loadLogInfo( "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " + "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}" ) if (!isCollecting.compareAndSet(false, true)) { loadLogInfo("collectFace 已在采集中,忽略重复触发") return } recognizeFail() return } retryFailCount++ loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount") } private fun recognizeFail() { countDownTimer?.cancel() binding.tvToInit.text = "" binding.tvSimilarValue.text = "" binding.llCollectButton.visible() binding.btnRetry.visible() binding.btnCollect.run { visible() text = "新用户采集" // if (tag == "1") { // binding.btnRetry.gone() // text = "重新采集" // tag = "1" // } else { // binding.btnRetry.visible() // "新用户采集" // tag = "1" // } } } /** * 根据相似度similar>设定阈值0.8作为识别成功判断, * 如果similar<0.3作为识别失败判断, * 如果similar>=0.3&&similar<=0.8时,暂时不作为失败情况,再识别5次如果similar<0.8则作为识别失败处理 */ private fun loadFaceRecognizeResult2(result: CompareResult?) { // 无有效结果直接忽略 val similar = result?.similar ?: return binding.tvSimilarValue.text = "$similar,${retryFailCount}次" loadLogInfo( "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " + "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount" ) if (similar >= faceSuccessThreshold) { val userId = result.faceEntity?.userName if (userId.isNullOrBlank()) { loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略") return } loadLogInfo( "collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}" ) // 成功后必须重置状态 retryFailCount = 0 isCollecting.set(false) isCollectFace = false currentUserId = userId // 防抖,防止重复开闸 openPlateDebouncer.debounce { openPlate(userId) } return } if (isCollectFace) { loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar") return } if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) { loadLogInfo( "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " + "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}" ) if (!isCollecting.compareAndSet(false, true)) { loadLogInfo("collectFace 已在采集中,忽略重复触发") return } retryFailCount = 0 isCollectFace = true collectFace()//采集人脸 return } retryFailCount++ loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount") } /** * 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果 */ private fun loadFaceRecognizeResult(result: CompareResult?) { val similar = result?.similar ?: 0f binding.tvSimilarValue.text = "$similar" val similarPass = result?.isSimilarPass ?: false if (similarPass.not()) { loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,开始采集") isCollectFace = true collectFace() return } currentUserId = result?.faceEntity?.userName if (tempUserId != currentUserId) { SoundPoolUtil.getInstance().play("success", 0) } // plateEnable = true Debouncer(5000).debounce { openPlate(currentUserId) } } /** * 查询餐盘数据 */ private fun getPlateData(block: (List) -> Unit) { MyApp.plateList?.let { block(it) } } private var currentPlateInfo: EquipmentUserInfo? = null private var plateEnable = false /** * 打开餐盘柜 */ private fun openPlate(userId: String?) { if (plateEnable.not()) return loadLogInfo("collectFace,openPlate: userId=$userId") getPlateData { items -> loadLogInfo("collectFace,openPlate: items:${Gson().toJson(items)}") // 未查询到当前用户,查询第一个未绑定的餐盘 val item = items.firstOrNull { it.plateNumber.isNullOrEmpty().not() } loadLogInfo("collectFace,openPlate: item2:${item}") if (item == null) { //暂无餐盘 loadLogInfo("collectFace,openPlate: 暂无餐盘,请联系管理人员,isCollectFace=$isCollectFace") //ToastUtils.showToast("暂无餐盘,请联系管理人员") SoundPoolUtil.getInstance().play(NO_PLATE_REMIND, 0) goInitActivity() return@getPlateData } // if (tempUserId != currentUserId) { SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0) // } else { // SoundPoolUtil.getInstance().play(COLLECT_SUCCESS, 0) // } loadLogInfo("collectFace,openPlate: item3:${item}") item.faceId = userId item.updateTime = DateTimeUtils.getDateTimeString() item.plateNumber = null plateEnable = false Debouncer(5000).debounce { //打开柜子 PlateUtils.open(this, item.equipmentBoxCode, false) countDownTimer?.cancel() goInitActivity() } currentPlateInfo = item MyApp.plateList = items SpTool.savePlateData(items) } } // private val debouncer by lazy { Debouncer(3000) } private fun initArcView() { //在布局结束后才做初始化操作 binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) recognizeViewModel.getCompareResultList().getValue() } override fun onDestroy() { loadLogInfo("onDestroy") instance = null if (rgbCameraHelper != null) { rgbCameraHelper!!.release() rgbCameraHelper = null } recognizeViewModel.destroy() EventBus.getDefault().unregister(this) super.onDestroy() } /** * 调整View的宽高,使预览显示正常且采集框固定为380x380 * * @param rgbPreview RGB预览View * @param previewView 显示预览数据的view * @param faceRectView 画框的view (380x380) * @param previewSize 预览大小 * @param displayOrientation 相机旋转角度 * @param scale 缩放比例 * @return 调整后的LayoutParams */ private fun adjustPreviewViewSize( rgbPreview: View, previewView: View, faceRectView: FaceRectView, previewSize: Camera.Size, displayOrientation: Int, scale: Float ): ViewGroup.LayoutParams { val layoutParams = previewView.layoutParams val measuredWidth = previewView.measuredWidth val measuredHeight = previewView.measuredHeight var ratio = (previewSize.height.toFloat()) / previewSize.width.toFloat() if (ratio > 1) { ratio = 1 / ratio } if (displayOrientation % 180 == 0) { layoutParams.width = measuredWidth layoutParams.height = (measuredWidth * ratio).toInt() } else { layoutParams.height = measuredHeight layoutParams.width = (measuredHeight * ratio).toInt() } if (scale < 1f) { val rgbParam = rgbPreview.getLayoutParams() layoutParams.width = (rgbParam.width * scale).toInt() layoutParams.height = (rgbParam.height * scale).toInt() } else { layoutParams.width = (layoutParams.width * scale).toInt() layoutParams.height = (layoutParams.height * scale).toInt() } val metrics = DisplayMetrics() windowManager.defaultDisplay.getMetrics(metrics) if (layoutParams.width >= metrics.widthPixels) { val viewRatio = layoutParams.width / (metrics.widthPixels.toFloat()) layoutParams.width = (layoutParams.width / viewRatio).toInt() layoutParams.height = (layoutParams.height / viewRatio).toInt() } if (layoutParams.height >= metrics.heightPixels) { val viewRatio = layoutParams.height / (metrics.heightPixels.toFloat()) layoutParams.width = (layoutParams.width / viewRatio).toInt() layoutParams.height = (layoutParams.height / viewRatio).toInt() } previewView.setLayoutParams(layoutParams) faceRectView.setLayoutParams(layoutParams) return layoutParams } private fun initRgbCamera() { val cameraListener: CameraListener = object : CameraListener { override fun onCameraOpened( camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean ) { loadLogInfo("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") runOnUiThread({ val previewSizeRgb = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, previewSizeRgb, displayOrientation, 0.6f ) loadLogInfo("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") loadLogInfo("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") loadLogInfo( "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ ConfigUtil.isDrawRgbRectHorizontalMirror( context ) }, isDrawRgbRectVerticalMirror = ${ ConfigUtil.isDrawRgbRectVerticalMirror( context ) }" ) rgbFaceRectTransformer = FaceRectTransformer( previewSizeRgb.width, previewSizeRgb.height, layoutParams.width, layoutParams.height, 90, cameraId, isMirror, true, true ) recognizeViewModel.onRgbCameraOpened(camera) recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer) }) } @RequiresApi(api = Build.VERSION_CODES.Q) override fun onPreview(nv21: ByteArray?, camera: Camera?) { if (!isRecognition) { return } binding.dualCameraFaceRectView.clearFaceInfo() facePreviewInfoList = recognizeViewModel.onPreviewFrame(nv21, true) if (facePreviewInfoList != null && rgbFaceRectTransformer != null) { drawPreviewInfo(facePreviewInfoList) } if (facePreviewInfoList.isNullOrEmpty()) { currentPlateInfo = null currentUserId = null userFaceInfo = null binding.btnCollect.tag = "" binding.tvSimilarValue.text = "" retryFailCount = 0 } recognizeViewModel.clearLeftFace(facePreviewInfoList) } override fun onCameraClosed() { Timber.i("initRgbCamera onCameraClosed: ") } override fun onCameraError(e: java.lang.Exception) { Timber.i("initRgbCamera onCameraError: %s", e.message) e.printStackTrace() } override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") if (rgbFaceRectTransformer != null) { rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") } } val previewConfig: PreviewConfig = recognizeViewModel.previewConfig rgbCameraHelper = DualCameraHelper.Builder() .previewViewSize( Point( binding.dualCameraTexturePreviewRgb.measuredWidth, binding.dualCameraTexturePreviewRgb.measuredHeight ) ) .rotation(windowManager.defaultDisplay.rotation) .additionalRotation(0) // 角度 .previewSize(recognizeViewModel.loadPreviewSize()) .specificCameraId(previewConfig.rgbCameraId) .isMirror(true) .previewOn(binding.dualCameraTexturePreviewRgb) .cameraListener(cameraListener) .build() rgbCameraHelper!!.init() rgbCameraHelper!!.start() } /** * 绘制RGB、IR画面的实时人脸信息 * * @param facePreviewInfoList RGB画面的实时人脸信息 */ private fun drawPreviewInfo(facePreviewInfoList: MutableList?) { if (rgbFaceRectTransformer != null) { val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( facePreviewInfoList, LivenessType.RGB, openRectInfoDraw ) // 识别成功 binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) } } fun openRectInfoDraw(view: View?) { openRectInfoDraw = !openRectInfoDraw recognizeViewModel.setDrawRectInfoTextValue(openRectInfoDraw) } override fun onGlobalLayout() { loadLogInfo("onGlobalLayout") binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) // 请求权限 checkCameraPermission() } private fun resumeCamera() { isRecognition = true if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { loadLogInfo("collectFace,resumeCamera: ") rgbCameraHelper!!.start() } else { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) } } /** * 临时用户id */ private var tempUserId: String = "" override fun onResume() { super.onResume() try { if (isFirstOpen) { goInitActivity() binding.root.postDelayed({ isFirstOpen = false }, 1000) return } resetRecognizeState() } catch (e: Exception) { e.printStackTrace() } } /** * 重置识别状态 */ private fun resetRecognizeState() { binding.llCollectButton.gone() retryFailCount = 0 binding.tvSimilarValue.text = "" isCollecting.set(false) isCollectFace = false plateEnable = true collectCount = 0 tempUserId = "${System.currentTimeMillis()}" resumeCamera() registerHandler = RegisterCallbackHandler() countDownTimer?.let { it.cancel() it.start() } } private var isFirstOpen = true protected override fun onPause() { pauseCamera() super.onPause() countDownTimer?.cancel() } private fun pauseCamera() { isRecognition = false recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) } override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) { super.handleLoginSuccess(equipmentUserInfo, isAdmin) // goInitActivity() } private val totalTimeInMillis = 15 * 1000L fun initCountTime() { countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) { override fun onTick(millisUntilFinished: Long) { loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}") binding.tvToInit.text = "${(millisUntilFinished / 1000).toInt() + 1}" } override fun onFinish() { if (facePreviewInfoList.isNullOrEmpty()) { goInitActivity() } else { recognizeFail() } } } } fun goInitActivity() { val intent = Intent(context, InitActivity::class.java) startActivity(intent) } /** * 采集人脸 */ private fun collectFace() { loadLogInfo("collectFace: -----------1,开始采集人脸,开始时间:${DateTimeUtils.getDateTimeString()}----------") if (isFirstOpen) return loadLogInfo("collectFace: ----------2,isFirstOpen=false-----------") loadLogInfo("collectFace: ----------4,检测到人脸信息-----------") currentUserId = null userFaceInfo = null recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY) loadLogInfo("collectFace: ----------5,人脸采集中-----------") } private val uploadFaceDebouncer = Debouncer(10000) /** * 改为上传人脸数据,不需要发生人脸数据到收银端 */ private fun sendFaceData(faceData: String, headBmp: Bitmap) { loadLogInfo("collectFace,sendFaceData: ----------11,发送人脸数据-----------") val imageFile = BitmapSaver.saveToAppFilesDir( headBmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" ) loadLogInfo("collectFace,sendFaceData:${imageFile?.absolutePath}") if (imageFile == null) { ToastUtils.showToast("获取人脸照片失败") return } uploadFaceDebouncer.debounce { addUserFace(faceData, imageFile) } // // 给某个客户端发送 // val jsonObject = JSONObject().also { // it.put("type", "faceFeature") // it.put("content", faceData) // } // tcpClient?.send(jsonObject) } private fun addUserFace(faceData: String, imageFile: File) { settingViewModel.addUserFace(faceData, imageFile) { status, faceModel -> loadLogInfo("collectFace,saveFaceInfo: ----------12,人脸数据发送完成时间:${DateTimeUtils.getDateTimeString()},结果:$status-----------") if (status.not()) { ToastUtils.showToast("新增数据失败") loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据失败,faceData=${faceData}") return@addUserFace } if (faceModel == null) { ToastUtils.showToast("新增数据成功,但未拿到接口数据") loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据成功但接口返回为null,faceData=${faceData}") return@addUserFace } Thread { saveFaceInfo(faceModel) uploadFaceDebouncer.reset() }.start() } } private fun getFaceDataBlock() { if (userFaceInfo?.faceFeature?.featureData == null) { loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据为null-----------") return } val faceData: String? = Base64.encode(userFaceInfo?.faceFeature?.featureData) if (faceData.isNullOrBlank()) { loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据Base64编码为空") return } loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------") val headBmp = userFaceInfo!!.headBmp //block(faceData, headBmp) isCollecting.set(false) isCollectFace = false sendFaceData(faceData, headBmp) } /** * 加载日志 * @param msg 日志内容 */ private fun loadLogInfo(msg: String) { Timber.tag(TAG).d(msg) LogFileUtil.saveLog(msg) } private var facePreviewInfoList: MutableList? = null private var userFaceInfo: UserFaceInfo? = null private var currentUserId: String? = null private fun saveFaceInfo(faceModel: UserFaceModel) { try { loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------") val faceDao = FaceDatabase.getInstance(this).faceDao() val faceEntity = FaceEntity( faceModel.userId, null, Base64.decode(faceModel.faceFeatureStr) ).also { //1-会员、2-临时用户 it.userType = if (faceModel.isMember) "1" else "2" } faceDao.insert(faceEntity) loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------") recognizeViewModel.refreshFaceList() loadLogInfo("collectFace,saveFaceInfo: ----------11,刷新人脸数据-----------") runOnUiThread { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) } loadLogInfo("---------------------------------") collectCount = 0 } catch (e: Exception) { e.printStackTrace() loadLogInfo("collectFace,saveFaceInfo: ----------13,人脸采集异常:${e.message}-----------") collectCount++ loadLogInfo("collectFace,saveFaceInfo: ----------14,${collectCount}次人脸采集异常:${e.message}-----------") ToastUtils.showToast("人脸采集异常:${e.message}") } } private var collectCount = 0 // private var tcpClient: TcpClient? = null // // private fun addSocketListener() { //// val ipAddress = NetworkUtils.getIPAddress(true) //// loadLogInfo( "addSocketListener,ipAddress: $ipAddress"); // tcpClient = TcpClient( // "192.168.1.95", // 5000, // GlobalData.deviceId, // clientId // 5000, // connectTimeoutMs // 10 * 1000, // heartbeatIntervalMs // 30 * 1000 // heartbeatTimeoutMs // ) // // tcpClient?.setListener(object : TcpClientListenerImpl() { // override fun onSendSuccess(json: JSONObject?) { // super.onSendSuccess(json) // loadLogInfo("collectFace,TcpClientListener发送成功:$json") //// toast("发送成功") // } // // override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) { // super.onSendFailed(json, e) // ToastUtils.showToast("发送失败${e?.message}") // loadLogInfo("collectFace,TcpClientListener人脸数据发送失败:${e?.message}") // } // // override fun onMessage(json: JSONObject?) { // super.onMessage(json) // loadLogInfo("collectFace,TcpClientListener收到消息:$json") // val type = json?.getInt("type") // // 根据服务端消息类型处理删除新增临时用户人脸数据--------------------- // when (type) { // LanServer.TYPE_ADD_FACE_DATA -> { // //采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据 // loadLogInfo("collectFace,TcpClientListener收到消息,新增用户:采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据") // } // // LanServer.TYPE_CLEAR_FACE_DATA -> { // clearFaceData() // } // // else -> {} // } // } // }) // tcpClient?.start() // // try { // LanServer.getInstance().let { // it.setListener(object : LanServerListenerImpl() { // override fun onMessageReceived(clientId: String?, message: JSONObject?) { // super.onMessageReceived(clientId, message) // ToastUtils.showToast("收到消息clientId=$clientId") // } // }) // it.start() // } // } catch (e: Exception) { // e.printStackTrace() // } // } private fun clearFaceData() { Thread { val faceDao = FaceDatabase.getInstance(this).faceDao() faceDao.deleteTempUserFaceData() recognizeViewModel.refreshFaceList() loadLogInfo("collectFace,TcpClientListener收到消息,已删除并刷新人脸数据") }.start() } private var isCollectFace = false }