diff --git a/app/src/main/java/com/sw/face/collect/InitActivity.kt b/app/src/main/java/com/sw/face/collect/InitActivity.kt index 989771f..f344301 100644 --- a/app/src/main/java/com/sw/face/collect/InitActivity.kt +++ b/app/src/main/java/com/sw/face/collect/InitActivity.kt @@ -9,6 +9,10 @@ class InitActivity : BaseActivity() { override fun initialize() { super.initialize() + + binding.btnStart.setOnClickListener { + finish() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/MainActivity.kt b/app/src/main/java/com/sw/face/collect/MainActivity.kt index 9c9f8c5..0f965ca 100644 --- a/app/src/main/java/com/sw/face/collect/MainActivity.kt +++ b/app/src/main/java/com/sw/face/collect/MainActivity.kt @@ -6,7 +6,9 @@ import android.content.pm.PackageManager 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.util.Log import android.view.View import android.view.ViewGroup @@ -14,13 +16,16 @@ import android.view.ViewTreeObserver import android.widget.FrameLayout import android.widget.Toast import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import com.arcsoft.face.ErrorInfo +import com.google.gson.Gson import com.sw.face.collect.base.BaseActivity import com.sw.face.collect.base.GlobalData import com.sw.face.collect.databinding.ActivityMainBinding +import com.sw.face.collect.ext.clickWithDebounce import com.sw.face.collect.ext.dp import com.sw.face.collect.ext.gone import com.sw.face.collect.ext.toast @@ -32,6 +37,7 @@ import com.sw.face.collect.utils.BitmapUtils import com.sw.face.collect.utils.Debouncer import com.sw.face.collect.utils.FaceEngineUtils import com.sw.face.collect.utils.IntervalExecutor +import com.sw.face.collect.utils.PermissionHelper import com.sw.face.collect.utils.RegisterCallbackHandler import com.sw.face.collect.utils.SoundPoolUtil import com.sw.face.collect.viewmodel.MainViewModel @@ -65,365 +71,242 @@ import kotlin.getValue import kotlin.system.exitProcess class MainActivity : BaseActivity(), ViewTreeObserver.OnGlobalLayoutListener { - // private val debouncer = Debouncer(2000) + + public val recognizeViewModel by viewModels() +// public val userViewModel by viewModels() + companion object { - private const val TAG = "MainActivity" + private const val TAG = "LoginByFaceActivity" private const val COLLECT_SUCCESS = "collect_success" private const val RECOGNIZE_SUCCESS = "recognize_success" private const val NO_PLATE_REMIND = "no_plate_remind"; } - // 虹软人脸配置 ⬇ - private var isRecognition = false - private var rgbCameraHelper: DualCameraHelper? = null - private var rgbFaceRectTransformer: FaceRectTransformer? = null - private var livenessType = LivenessType.IR - - // private val livenessType = LivenessType.RGB - private var openRectInfoDraw = false - - private val nv21ToBitmap by lazy { NV21ToBitmap(this) } - - // - private var irCameraHelper: DualCameraHelper? = null - private var irFaceRectTransformer: FaceRectTransformer? = null - - private val recognizeViewModel: RecognizeViewModel by lazy { viewModels().value } - - private val mainViewModel by viewModels() - - private var currentUserId: String? = null - private var lastFaceTrackId = -1 + 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() = ActivityMainBinding.inflate(layoutInflater) - override fun onGlobalLayout() { - Log.d(TAG, "onGlobalLayout") - binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) - openCamera() - } +// private var logList: MutableList = mutableListOf() +// private val logAdapter by lazy { LogAdapter(logList) } - private var pageNo = 1 override fun initialize() { - super.initialize() - //mainViewModel.getUserFaceCache(pageNo = pageNo) FaceEngineUtils.activeEngine() - - binding.btnCollectFace.setOnClickListener { - registerHandler = RegisterCallbackHandler() -// requestSinglePermissionResult(Manifest.permission.CAMERA) { granted -> -// if (granted.not()) { -// toast("没有相机权限") -// return@requestSinglePermissionResult -// } -// } - } - binding.tvTitle.setOnClickListener { finish() } - //addSocketListener() - - binding.flFace.visible() - binding.flRecognizeIr.visible() - binding.flCameraView.visible() - binding.tvFaceTip.visible() - binding.layoutState.gone() - -// resumeCamera() -// startFaceTask() - -// startActivity(Intent(this, InitActivity::class.java)) - +// 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() + } } -// requestSinglePermissionResult(Manifest.permission.CAMERA) { granted -> -// if (granted.not()) { -// toast("没有相机权限") -// return@requestSinglePermissionResult -// } + + + private fun checkCameraPermission() { + //Timber.i("checkCameraPermission") + val permissionHelper = + PermissionHelper.with(this, REQUIRED_PERMISSIONS, CAMERA_PERMISSION_REQUEST_CODE) + .onGranted { + loadLogInfo("checkCameraPermission 权限已允许") + // 权限已授予 +// recognizeViewModel.init() +// initRgbCamera() + recognizeViewModel.init( + PreviewConfig( + PreviewConfig.DEFAULT_IR_CAMERA_ID, + PreviewConfig.DEFAULT_RGB_CAMERA_ID, + 0, + 0 + ) + ) + initRgbCamera() + if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { + initIrCamera() + } + 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 fun openCamera() { - Log.d(TAG, "openCamera") - if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != - PackageManager.PERMISSION_GRANTED - ) { - toast("无摄像头权限") - return - } - try { -// val cameraCount = Camera.getNumberOfCameras() -// if (cameraCount < 3) { -// toast("摄像头数量异常"+cameraCount) -// return -// } - recognizeViewModel.init( - PreviewConfig( - PreviewConfig.DEFAULT_IR_CAMERA_ID, PreviewConfig.DEFAULT_RGB_CAMERA_ID, - 0, 0 - ) - ) - initRgbCamera() - if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { - initIrCamera() - } - } catch (e: Exception) { - Log.d(TAG, "打开摄像头失败") - toast("打开摄像头失败+${e.message}") - } - } - - - /** - * 启用虹软人脸识别 - */ - fun setupArcCamera() { - initArcViewModel() - initArcView() - openRectInfoDraw = true - } - - /** - * 改为上传人脸数据,不需要发生人脸数据到收银端 - */ - 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 var collectCount = 0 - private fun saveFaceInfo(faceModel: UserFaceModel) { - try { - loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------") - val faceDao = FaceDatabase.getInstance(this).faceDao() - val faceEntity = FaceEntity( - faceModel.userId, - null, - com.sw.plate.utils.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 fun addSocketListener() { -// //val ipAddress = NetworkUtils.getIPAddress(true) -// //Log.d(TAG, "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) -//// toast("发送成功") -// } -// -// override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) { -// super.onSendFailed(json, e) -// toast("发送失败${e?.message}") -// } -// -// override fun onMessage(json: JSONObject?) { -// super.onMessage(json) -// -// val type = json?.getInt("type") -// // 根据服务端消息类型处理删除新增临时用户人脸数据--------------------- -// when (type) { -// LanServer.TYPE_ADD_FACE_DATA -> { -// //采集设备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) -// toast("收到消息clientId=$clientId") -// } -// }) -// it.start() -// } -// } catch (e: Exception) { -// e.printStackTrace() -// } -// } - - val executor = IntervalExecutor() - - public override fun onStop() { - rgbCameraHelper?.release() - rgbCameraHelper = null - irCameraHelper?.release() - irCameraHelper = null - recognizeViewModel.destroy() - super.onStop() - } - - fun resumeCamera() { - Log.d(TAG, "resumeCamera isRecognition = $isRecognition") - isRecognition = true - if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { - rgbCameraHelper!!.start() - } - } - - fun pauseCamera() { - Log.d(TAG, "pauseCamera isRecognition = $isRecognition") - isRecognition = false - - recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) - } - - private var userFaceInfo: UserFaceInfo? = null - private var isCheckCamera = false - private var isCollectFace = false - private val isCollecting = AtomicBoolean(false) + private var isRecognition = false + private var rgbCameraHelper: DualCameraHelper? = null + private var rgbFaceRectTransformer: FaceRectTransformer? = null + private val livenessType = LivenessType.IR + private var openRectInfoDraw = false + private val regDebouncer = Debouncer(3000) private var registerHandler: RegisterCallbackHandler? = null - private fun initArcViewModel() { - if (DualCameraHelper.hasDualCamera()) { - livenessType = LivenessType.IR - isCheckCamera = true - } else { - livenessType = LivenessType.RGB - isCheckCamera = false - } - recognizeViewModel.setLiveType(livenessType) + 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 -> - Log.d(TAG, "initArcViewModel: userFaceInfo == null : ${userFaceInfo == null}") registerHandler?.setCallback { if (!isCollectFace) { return@setCallback } - if (this.userFaceInfo == null && userFaceInfo != null) { - Toast.makeText(this@MainActivity, "开始采集", Toast.LENGTH_SHORT).show() - loadLogInfo("collectFace:开始采集------------") + 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 { faceData, headBmp -> - isCollecting.set(false) - isCollectFace = false - loadLogInfo("collectFace:采集完成,准备上传------------") - sendFaceData(faceData, headBmp) - } + getFaceDataBlock() } } } recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? -> if (ftInitCode != ErrorInfo.MOK) { - val error: String? = context.getString( - R.string.specific_engine_init_failed, "ftEngine", - ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) + val error: String? = getString( + R.string.specific_engine_init_failed, + "ftEngine", + ftInitCode, + ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) ) - Log.d(TAG, "ftInitCode observe = $error") + //Timber.e("ftInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.frInitCode.observe(this, Observer { frInitCode: Int? -> if (frInitCode != ErrorInfo.MOK) { - val error: String? = context.getString( - R.string.specific_engine_init_failed, "frEngine", - frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) + val error: String? = getString( + R.string.specific_engine_init_failed, + "frEngine", + frInitCode, + ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) ) - Log.d(TAG, "frInitCode observe = $error") + //Timber.e("frInitCode observe = $error") ToastUtils.showToast(error) } }) recognizeViewModel.flInitCode.observe(this, Observer { flInitCode: Int? -> if (flInitCode != ErrorInfo.MOK) { - val error: String? = context.getString( - R.string.specific_engine_init_failed, "flEngine", - flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) + val error: String? = getString( + R.string.specific_engine_init_failed, + "flEngine", + flInitCode, + ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) ) - Log.d(TAG, "flInitCode observe = $error") + //Timber.e("flInitCode observe = $error") ToastUtils.showToast(error) } }) - recognizeViewModel.recognizeConfiguration - .observe(this, Observer { recognizeConfiguration: RecognizeConfiguration? -> - Log.d(TAG, "recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") - }) + recognizeViewModel.recognizeConfiguration.observe( + this, + Observer { recognizeConfiguration: RecognizeConfiguration? -> + //Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") + }) recognizeViewModel.recognizeNotice.observe(this, Observer { notice: String? -> - Log.d(TAG, "recognizeNotice observe notice = $notice") + //Timber.i("recognizeNotice observe notice = $notice") }) - recognizeViewModel.recognizeUserId.observe( - this, - Observer { compareResult: CompareResult? -> -// Log.d( -// TAG, -// "recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}" -// ) -// lastFaceTrackId = compareResult.trackId -// val faceEntity = compareResult.faceEntity -// currentUserId = faceEntity.userName -// if (!currentUserId.isNullOrBlank()) { -// toast("您已采集过人脸信息") -// return@Observer -// } -//// //未识别到,拍摄照片 - loadFaceRecognizeResult2(compareResult) - }) + recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? -> + loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}") + loadFaceRecognizeResult3(result) + }) recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> - Log.d(TAG, "drawRectInfoText observe info = $info") + //Timber.i("drawRectInfoText observe info = $info") }) } + /** + * 人脸识别开关,true-loadFaceRecognizeResult2,false-loadFaceRecognizeResult + */ + private var faceUseSwitch = true /** * 识别失败重试次数 @@ -445,6 +328,84 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo */ 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.llCollectButton.visible() + binding.btnRetry.visible() + binding.btnCollect.run { + visible() + text = "新用户采集" +// if (tag == "1") { +// binding.btnRetry.gone() +// text = "重新采集" +// tag = "1" +// } else { +// binding.btnRetry.visible() +// text = "新用户采集" +// tag = "1" +// } + } + } + /** * 根据相似度similar>设定阈值0.8作为识别成功判断, * 如果similar<0.3作为识别失败判断, @@ -456,8 +417,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo binding.tvSimilarValue.text = "$similar,${retryFailCount}次" loadLogInfo( - "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " + - "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount" + "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " + "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount" ) if (similar >= faceSuccessThreshold) { @@ -477,13 +437,9 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo isCollectFace = false currentUserId = userId -// // 防抖,防止重复开闸 - Debouncer(5000).debounce { -// countDownTimer?.cancel() - binding.tvCountDown.text = "" - SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0) - //openPlate(userId) - //toast("打开餐盘,用户id=$userId") + // 防抖,防止重复开闸 + openPlateDebouncer.debounce { + openPlate(userId) } return } @@ -495,8 +451,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) { loadLogInfo( - "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " + - "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}" + "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " + "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}" ) if (!isCollecting.compareAndSet(false, true)) { loadLogInfo("collectFace 已在采集中,忽略重复触发") @@ -506,7 +461,6 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo retryFailCount = 0 isCollectFace = true collectFace()//采集人脸 -// toast("开始采集人脸") return } @@ -515,27 +469,92 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo } /** - * 采集人脸 + * 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果 */ - 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,人脸采集中-----------") - - countDown() + 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 var plateEnable = false + + /** + * 打开餐盘柜 + */ + private fun openPlate(userId: String?) { + if (plateEnable.not()) return + SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0) + countDownTimer?.cancel() + goInitActivity() +// 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) +// } +// 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") + if (rgbCameraHelper != null) { + rgbCameraHelper!!.release() + rgbCameraHelper = null + } + + recognizeViewModel.destroy() + +// EventBus.getDefault().unregister(this) + super.onDestroy() + } + /** * 调整View的宽高,使预览显示正常且采集框固定为 * @@ -565,7 +584,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo return layoutParams } - private fun initRgbCamera() { + private fun initRgbCamera() { val cameraListener: CameraListener = object : CameraListener { override fun onCameraOpened( camera: Camera, @@ -627,8 +646,8 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo }) } + @RequiresApi(api = Build.VERSION_CODES.Q) override fun onPreview(nv21: ByteArray?, camera: Camera?) { -// faceByteArray = nv21 if (!isRecognition) { return } @@ -652,73 +671,372 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo L.e("摄像头正常") } binding.dualCameraFaceRectView.clearFaceInfo() - val facePreviewInfoList: MutableList? = - recognizeViewModel.onPreviewFrame(nv21, true) - if (facePreviewInfoList != null - && rgbFaceRectTransformer != null - ) { + 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 = "" - registerHandler = RegisterCallbackHandler() + retryFailCount = 0 } recognizeViewModel.clearLeftFace(facePreviewInfoList) } override fun onCameraClosed() { - Log.d(TAG, "initRgbCamera onCameraClosed: ") + //Timber.i("initRgbCamera onCameraClosed: ") } override fun onCameraError(e: java.lang.Exception) { - Log.d(TAG, "initRgbCamera onCameraError: ${e.message}") + //Timber.i("initRgbCamera onCameraError: %s", e.message) e.printStackTrace() } override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { - Log.d( - TAG, - "initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}" - ) + //Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") if (rgbFaceRectTransformer != null) { rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } - Log.d( - TAG, - "initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation" - ) + //Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") } } - val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth - val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight - Log.d(TAG, "initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight") - val previewConfig = recognizeViewModel.previewConfig - rgbCameraHelper = DualCameraHelper.Builder() - .previewViewSize(Point(measuredWidth, measuredHeight)) -// .previewViewSize(Point(608.dp, 456.dp)) -// .previewViewSize(Point(1024, 768)) - .rotation(windowManager.defaultDisplay.rotation) - .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度 + 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()) -// .previewSize(Point(1080, 720)) - .specificCameraId(previewConfig.rgbCameraId) - .isMirror(true) - .previewOn(binding.dualCameraTexturePreviewRgb) - .cameraListener(cameraListener) - .build() - rgbCameraHelper?.run { - init() -// setSurfaceFrameCallback { bitmap -> -// imageBitmap = BitmapFlipUtil.flipHorizontally(bitmap) -// -//// binding.ivRecImage.visible() -//// binding.ivRecImage.setImageBitmap(imageBitmap) -// } - start() + .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) + } + + + private var countDownTimer: CountDownTimer? = null + 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 val mainViewModel by viewModels() + private fun addUserFace(faceData: String, imageFile: File) { + mainViewModel.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? = + com.sw.plate.utils.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) { + Log.d(TAG, "loadLogInfo: $msg") + //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, com.sw.plate.utils.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 isCheckCamera = false + private var isCollectFace = false + private val nv21ToBitmap by lazy { NV21ToBitmap(this) } + + private var irCameraHelper: DualCameraHelper? = null + private var irFaceRectTransformer: FaceRectTransformer? = null + /** * 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用 */ @@ -729,10 +1047,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo } val irCameraListener: CameraListener = object : CameraListener { override fun onCameraOpened( - camera: Camera, - cameraId: Int, - displayOrientation: Int, - isMirror: Boolean + camera: Camera, cameraId: Int, displayOrientation: Int, isMirror: Boolean ) { Log.d( TAG, @@ -741,15 +1056,22 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo val previewSizeIr = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, - binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr, - previewSizeIr, displayOrientation, 0.25f + binding.dualCameraTexturePreviewIr, + binding.dualCameraFaceRectViewIr, + previewSizeIr, + displayOrientation, + 0.25f ) irFaceRectTransformer = FaceRectTransformer( - previewSizeIr.width, previewSizeIr.height, + previewSizeIr.width, + previewSizeIr.height, // layoutParams.width, layoutParams.height, - layoutParams.width, layoutParams.height, - displayOrientation, cameraId, isMirror, + layoutParams.width, + layoutParams.height, + displayOrientation, + cameraId, + isMirror, ConfigUtil.isDrawIrRectHorizontalMirror(context), ConfigUtil.isDrawIrRectVerticalMirror(context) ) @@ -773,8 +1095,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo } override fun onCameraConfigurationChanged( - cameraID: Int, - displayOrientation: Int + cameraID: Int, displayOrientation: Int ) { if (irFaceRectTransformer != null) { irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation @@ -787,8 +1108,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo } val previewConfig = recognizeViewModel.previewConfig - irCameraHelper = DualCameraHelper.Builder() - .previewViewSize( + irCameraHelper = DualCameraHelper.Builder().previewViewSize( Point( binding.dualCameraTexturePreviewIr.measuredWidth, binding.dualCameraTexturePreviewIr.measuredHeight @@ -797,8 +1117,7 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo // .previewViewSize(Point(4,3)) .rotation(windowManager.defaultDisplay.rotation) .specificCameraId(previewConfig.irCameraId) - .previewOn(binding.dualCameraTexturePreviewIr) - .cameraListener(irCameraListener) + .previewOn(binding.dualCameraTexturePreviewIr).cameraListener(irCameraListener) .isMirror(true) .previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小 .additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度 @@ -811,37 +1130,6 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo } } - /** - * 绘制RGB、IR画面的实时人脸信息 - * - * @param facePreviewInfoList RGB画面的实时人脸信息 - */ - private fun drawPreviewInfo(facePreviewInfoList: MutableList) { -// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}") - if (rgbFaceRectTransformer != null) { - val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( - facePreviewInfoList, - LivenessType.RGB, - openRectInfoDraw - ) - // 识别成功 - binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) - } - - val listIsEmpty = facePreviewInfoList.isEmpty() - val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null - Log.d( - TAG, - "listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId" - ) - if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) { - if (lastFaceTrackId != -1 && currentUserId != null) { - Log.d(TAG, "$lastFaceTrackId 用户离开") - lastFaceTrackId = -1 - currentUserId = null - } - } - } fun switchCamera() { try { @@ -881,203 +1169,4 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo finish() exitProcess(0) } - - override fun onDestroy() { - countDownJob?.cancel() - super.onDestroy() - } - -// private var countDownJob: Job? = null -// private fun countDown() { -// countDownJob = countDownByFlow( -// total = 3, -// scope = lifecycleScope, -// onStart = { -// binding.tvCollectState.text = "采集完成(3s)" -// }, -// onTick = { seconds -> -// binding.tvCollectState.text = "采集完成(${seconds}s)" -// }, -// onFinish = { -// //binding.tvCollectState.text = "采集完成" -// currentUserId = null -// binding.layoutState.gone() -// binding.tvFaceTip.visible() -//// binding.btnCollectFace.visible() -// } -// ) -// } - - private val intervalExecutor by lazy { IntervalExecutor() } -// private var faceTaskJob: Job? = null -// -// private var taskPageNo = 1 -// fun startFaceTask() { -// faceTaskJob = -// intervalExecutor.startIntervalTaskWithInitialDelay(60 * 1000L, 30 * 1000L) { -// val timestamp = SpTool.getLastFaceTimestamp() -// if (timestamp == 0L) { -// return@startIntervalTaskWithInitialDelay -// } -// mainViewModel.getFaceIncrementList( -// pageNo = taskPageNo, -// timestamp = timestamp, -// onAllQueryFinished = { -// taskPageNo = 1 -// }, -// onPageQueryFinished = { list -> -// runOnUiThread { -// if (list.isEmpty()) { -// return@runOnUiThread -// } -// updateFaceData(list) -// } -// } -// ) -// } -// } - - private fun updateFaceData(list: List) { - Thread { - val faceList = mutableListOf() - try { - list.forEach { model -> - if (model.faceDeleted == true) { - //删除数据 - FaceDatabase.getInstance(this).faceDao().deleteFaceById(model.userId) - } else { - //保存数据 - val faceEntity = FaceEntity( - model.userId, - null, - Base64.decode(model.faceFeatureStr) - ).also { - it.userType = "1" - } - faceList.add(faceEntity) - } - } - } catch (e: Exception) { - e.printStackTrace() - } - try { - if (faceList.isNotEmpty()) { - FaceDatabase.getInstance(this).faceDao().insert(faceList) - } - recognizeViewModel.refreshFaceList(); - } catch (e: Exception) { - e.printStackTrace() - } - }.start() - } - - private fun clearFaceData() { - Thread { - val faceDao = FaceDatabase.getInstance(this).faceDao() - faceDao.deleteTempUserFaceData() - recognizeViewModel.refreshFaceList(); - }.start() - } - - override fun onResume() { - super.onResume() - setupArcCamera() - registerHandler = RegisterCallbackHandler() - resumeCamera() - } - - override fun onPause() { -// countDownTimer?.cancel() - super.onPause() - pauseCamera() - } - - - private fun addUserFace(faceData: String, imageFile: File) { - mainViewModel.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 val uploadFaceDebouncer = Debouncer(10000) - - private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) { - if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) { - loadLogInfo("collectFace:数据为空------11111------") - return - } - val faceData: String? = - com.sw.plate.utils.Base64.encode(userFaceInfo?.faceFeature?.featureData) - if (faceData.isNullOrBlank()) { - loadLogInfo("collectFace:数据为空------22222------") - return - } - loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------") - block(faceData, userFaceInfo!!.headBmp) - } - - /** - * 加载日志 - * @param msg 日志内容 - */ - private fun loadLogInfo(msg: String) { -// Timber.tag(TAG).d(msg) -// LogFileUtil.saveLog(msg) - Log.d(TAG, "loadLogInfo: $msg") - } - - private val total = 30 - private var countDownJob: Job? = null - private fun countDown() { -//// countDownJob = countDownByFlow( -//// total = total, -//// scope = lifecycleScope, -//// onStart = { -//// binding.tvCountDown.text = "${total}s" -//// }, -//// onTick = { seconds -> -//// binding.tvCountDown.text = "${seconds}s" -//// }, -//// onFinish = { -//// } -//// ) -// if (countDownTimer == null) { -// initCountTime() -// } -// countDownDebounce.debounce { -// countDownTimer?.start() -// } - } - - private val countDownDebounce = Debouncer(10000) - - // private var countDownTimer:CountDownTimer?=null -// -// private val totalTimeInMillis = 30 * 1000L - fun initCountTime() { - //countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) { - // override fun onTick(millisUntilFinished: Long) { - // loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}") - // binding.tvCountDown.text = "${(millisUntilFinished / 1000).toInt() + 1}s" - // } -// - // override fun onFinish() { - // } - //} - } - } \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/MainActivityBak3.kt b/app/src/main/java/com/sw/face/collect/MainActivityBak3.kt new file mode 100644 index 0000000..f5618cc --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/MainActivityBak3.kt @@ -0,0 +1,1083 @@ +//package com.sw.face.collect +// +//import android.Manifest +//import android.content.Intent +//import android.content.pm.PackageManager +//import android.graphics.Bitmap +//import android.graphics.Point +//import android.hardware.Camera +//import android.os.CountDownTimer +//import android.util.Log +//import android.view.View +//import android.view.ViewGroup +//import android.view.ViewTreeObserver +//import android.widget.FrameLayout +//import android.widget.Toast +//import androidx.activity.viewModels +//import androidx.core.content.ContextCompat +//import androidx.lifecycle.Observer +//import androidx.lifecycle.lifecycleScope +//import com.arcsoft.face.ErrorInfo +//import com.sw.face.collect.base.BaseActivity +//import com.sw.face.collect.base.GlobalData +//import com.sw.face.collect.databinding.ActivityMainBinding +//import com.sw.face.collect.ext.dp +//import com.sw.face.collect.ext.gone +//import com.sw.face.collect.ext.toast +//import com.sw.face.collect.ext.visible +//import com.sw.face.collect.model.UserFaceModel +//import com.sw.face.collect.utils.Base64 +//import com.sw.face.collect.utils.BitmapSaver +//import com.sw.face.collect.utils.BitmapUtils +//import com.sw.face.collect.utils.Debouncer +//import com.sw.face.collect.utils.FaceEngineUtils +//import com.sw.face.collect.utils.IntervalExecutor +//import com.sw.face.collect.utils.RegisterCallbackHandler +//import com.sw.face.collect.utils.SoundPoolUtil +//import com.sw.face.collect.viewmodel.MainViewModel +//import com.sw.inbound.utils.DateTimeUtils +//import com.sw.plate.utils.L +//import com.sw.plate.utils.NV21ToBitmap +//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 kotlinx.coroutines.Job +//import kotlinx.coroutines.runBlocking +//import org.json.JSONObject +//import java.io.File +//import java.util.concurrent.atomic.AtomicBoolean +//import kotlin.getValue +//import kotlin.system.exitProcess +// +//class MainActivityBak3 : BaseActivity(), ViewTreeObserver.OnGlobalLayoutListener { +// // private val debouncer = Debouncer(2000) +// +// companion object { +// private const val TAG = "MainActivity" +// +// private const val COLLECT_SUCCESS = "collect_success" +// private const val RECOGNIZE_SUCCESS = "recognize_success" +// private const val NO_PLATE_REMIND = "no_plate_remind"; +// } +// +// // 虹软人脸配置 ⬇ +// private var isRecognition = false +// private var rgbCameraHelper: DualCameraHelper? = null +// private var rgbFaceRectTransformer: FaceRectTransformer? = null +// +// private var livenessType = LivenessType.IR +// +// // private val livenessType = LivenessType.RGB +// private var openRectInfoDraw = false +// +// private val nv21ToBitmap by lazy { NV21ToBitmap(this) } +// +// // +// private var irCameraHelper: DualCameraHelper? = null +// private var irFaceRectTransformer: FaceRectTransformer? = null +// +// private val recognizeViewModel: RecognizeViewModel by lazy { viewModels().value } +// +// private val mainViewModel by viewModels() +// +// private var currentUserId: String? = null +// private var lastFaceTrackId = -1 +// +// override fun inflateViewBinding() = ActivityMainBinding.inflate(layoutInflater) +// +// override fun onGlobalLayout() { +// Log.d(TAG, "onGlobalLayout") +// binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) +// openCamera() +// } +// +// private var pageNo = 1 +// override fun initialize() { +// super.initialize() +// //mainViewModel.getUserFaceCache(pageNo = pageNo) +// FaceEngineUtils.activeEngine() +// +// binding.btnCollectFace.setOnClickListener { +// registerHandler = RegisterCallbackHandler() +//// requestSinglePermissionResult(Manifest.permission.CAMERA) { granted -> +//// if (granted.not()) { +//// toast("没有相机权限") +//// return@requestSinglePermissionResult +//// } +//// } +// } +// binding.tvTitle.setOnClickListener { finish() } +// //addSocketListener() +// +// binding.flFace.visible() +// binding.flRecognizeIr.visible() +// binding.flCameraView.visible() +// binding.tvFaceTip.visible() +// binding.layoutState.gone() +// +//// resumeCamera() +//// startFaceTask() +// +//// startActivity(Intent(this, InitActivity::class.java)) +// +// initCountTime() +// +// 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) +// } +//// requestSinglePermissionResult(Manifest.permission.CAMERA) { granted -> +//// if (granted.not()) { +//// toast("没有相机权限") +//// return@requestSinglePermissionResult +//// } +//// } +// +// private fun openCamera() { +// Log.d(TAG, "openCamera") +// if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != +// PackageManager.PERMISSION_GRANTED +// ) { +// toast("无摄像头权限") +// return +// } +// try { +//// val cameraCount = Camera.getNumberOfCameras() +//// if (cameraCount < 3) { +//// toast("摄像头数量异常"+cameraCount) +//// return +//// } +// recognizeViewModel.init( +// PreviewConfig( +// PreviewConfig.DEFAULT_IR_CAMERA_ID, PreviewConfig.DEFAULT_RGB_CAMERA_ID, +// 0, 0 +// ) +// ) +// initRgbCamera() +// if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { +// initIrCamera() +// } +// } catch (e: Exception) { +// Log.d(TAG, "打开摄像头失败") +// toast("打开摄像头失败+${e.message}") +// } +// } +// +// +// /** +// * 启用虹软人脸识别 +// */ +// fun setupArcCamera() { +// initArcViewModel() +// initArcView() +// openRectInfoDraw = true +// } +// +// /** +// * 改为上传人脸数据,不需要发生人脸数据到收银端 +// */ +// 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 var collectCount = 0 +// private fun saveFaceInfo(faceModel: UserFaceModel) { +// try { +// loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------") +// val faceDao = FaceDatabase.getInstance(this).faceDao() +// val faceEntity = FaceEntity( +// faceModel.userId, +// null, +// com.sw.plate.utils.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 fun addSocketListener() { +//// //val ipAddress = NetworkUtils.getIPAddress(true) +//// //Log.d(TAG, "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) +////// toast("发送成功") +//// } +//// +//// override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) { +//// super.onSendFailed(json, e) +//// toast("发送失败${e?.message}") +//// } +//// +//// override fun onMessage(json: JSONObject?) { +//// super.onMessage(json) +//// +//// val type = json?.getInt("type") +//// // 根据服务端消息类型处理删除新增临时用户人脸数据--------------------- +//// when (type) { +//// LanServer.TYPE_ADD_FACE_DATA -> { +//// //采集设备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) +//// toast("收到消息clientId=$clientId") +//// } +//// }) +//// it.start() +//// } +//// } catch (e: Exception) { +//// e.printStackTrace() +//// } +//// } +// +// val executor = IntervalExecutor() +// +// public override fun onStop() { +// rgbCameraHelper?.release() +// rgbCameraHelper = null +// irCameraHelper?.release() +// irCameraHelper = null +// recognizeViewModel.destroy() +// super.onStop() +// } +// +// fun resumeCamera() { +// Log.d(TAG, "resumeCamera isRecognition = $isRecognition") +// isRecognition = true +// if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { +// rgbCameraHelper!!.start() +// } +// } +// +// fun pauseCamera() { +// Log.d(TAG, "pauseCamera isRecognition = $isRecognition") +// isRecognition = false +// +// recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) +// } +// +// private var userFaceInfo: UserFaceInfo? = null +// private var isCheckCamera = false +// private var isCollectFace = false +// private val isCollecting = AtomicBoolean(false) +// private var registerHandler: RegisterCallbackHandler? = null +// private fun initArcViewModel() { +// if (DualCameraHelper.hasDualCamera()) { +// livenessType = LivenessType.IR +// isCheckCamera = true +// } else { +// livenessType = LivenessType.RGB +// isCheckCamera = false +// } +// recognizeViewModel.setLiveType(livenessType) +// +// recognizeViewModel.prepareRegister() +// recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo -> +// Log.d(TAG, "initArcViewModel: userFaceInfo == null : ${userFaceInfo == null}") +// registerHandler?.setCallback { +// if (!isCollectFace) { +// return@setCallback +// } +// if (this.userFaceInfo == null && userFaceInfo != null) { +// Toast.makeText(this@MainActivityBak3, "开始采集", Toast.LENGTH_SHORT).show() +// loadLogInfo("collectFace:开始采集------------") +// recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE) +// this.userFaceInfo = userFaceInfo +// getFaceDataBlock { faceData, headBmp -> +// isCollecting.set(false) +// isCollectFace = false +// loadLogInfo("collectFace:采集完成,准备上传------------") +// sendFaceData(faceData, headBmp) +// } +// } +// } +// } +// recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? -> +// if (ftInitCode != ErrorInfo.MOK) { +// val error: String? = context.getString( +// R.string.specific_engine_init_failed, "ftEngine", +// ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) +// ) +// Log.d(TAG, "ftInitCode observe = $error") +// ToastUtils.showToast(error) +// } +// }) +// recognizeViewModel.frInitCode.observe(this, Observer { frInitCode: Int? -> +// if (frInitCode != ErrorInfo.MOK) { +// val error: String? = context.getString( +// R.string.specific_engine_init_failed, "frEngine", +// frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) +// ) +// Log.d(TAG, "frInitCode observe = $error") +// ToastUtils.showToast(error) +// } +// }) +// recognizeViewModel.flInitCode.observe(this, Observer { flInitCode: Int? -> +// if (flInitCode != ErrorInfo.MOK) { +// val error: String? = context.getString( +// R.string.specific_engine_init_failed, "flEngine", +// flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) +// ) +// Log.d(TAG, "flInitCode observe = $error") +// ToastUtils.showToast(error) +// } +// }) +// +// recognizeViewModel.recognizeConfiguration +// .observe(this, Observer { recognizeConfiguration: RecognizeConfiguration? -> +// Log.d(TAG, "recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") +// }) +// recognizeViewModel.recognizeNotice.observe(this, Observer { notice: String? -> +// Log.d(TAG, "recognizeNotice observe notice = $notice") +// }) +// +// recognizeViewModel.recognizeUserId.observe( +// this, +// Observer { compareResult: CompareResult? -> +//// Log.d( +//// TAG, +//// "recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}" +//// ) +//// lastFaceTrackId = compareResult.trackId +//// val faceEntity = compareResult.faceEntity +//// currentUserId = faceEntity.userName +//// if (!currentUserId.isNullOrBlank()) { +//// toast("您已采集过人脸信息") +//// return@Observer +//// } +////// //未识别到,拍摄照片 +// loadFaceRecognizeResult2(compareResult) +// }) +// +// recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> +// Log.d(TAG, "drawRectInfoText observe info = $info") +// }) +// } +// +// +// /** +// * 识别失败重试次数 +// */ +// private var retryFailCount = 0 +// +// /** +// * 识别失败最大次数 +// */ +// private val maxFailCount = 5 +// +// /** +// * 人脸失败阈值 +// */ +// private val faceFailThreshold = 0.3 +// +// /** +// * 配置阈值,默认值为0.8 +// */ +// private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) } +// +// /** +// * 根据相似度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 +// +//// // 防抖,防止重复开闸 +// Debouncer(5000).debounce { +//// countDownTimer?.cancel() +// binding.tvCountDown.text = "" +// SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0) +// //openPlate(userId) +// //toast("打开餐盘,用户id=$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()//采集人脸 +//// toast("开始采集人脸") +// return +// } +// +// retryFailCount++ +// loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount") +// } +// +// /** +// * 采集人脸 +// */ +// 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,人脸采集中-----------") +// +// countDown() +// } +// +// private fun initArcView() { +// //在布局结束后才做初始化操作 +// binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) +// recognizeViewModel.getCompareResultList().getValue() +// } +// +// /** +// * 调整View的宽高,使预览显示正常且采集框固定为 +// * +// * @param rgbPreview RGB预览View +// * @param previewView 显示预览数据的view +// * @param faceRectView 画框的view +// * @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 w = 640.dp//300.dp +// val w = 640.dp//300.dp +// val h = 480.dp//510.dp +// val layoutParams = FrameLayout.LayoutParams(w, h); +// 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 +// ) { +// Log.d( +// TAG, +// "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.5F +// //0.6F +// ) +// +// Log.d( +// TAG, +// "initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}" +// ) +// Log.d( +// TAG, +// "initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}" +// ) +// Log.d( +// TAG, +// "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ +// ConfigUtil.isDrawRgbRectHorizontalMirror( +// context +// ) +// }, isDrawRgbRectVerticalMirror = ${ +// ConfigUtil.isDrawRgbRectVerticalMirror( +// context +// ) +// }" +// ) +// // 调整识别窗口位置 +// rgbFaceRectTransformer = FaceRectTransformer( +// previewSizeRgb.width, +// previewSizeRgb.height, +// layoutParams.width, +// layoutParams.height, +// displayOrientation, +//// 90, +// cameraId, +// isMirror, +// true, +// false +// ) +// +// recognizeViewModel.onRgbCameraOpened(camera) +// recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer) +// }) +// } +// +// override fun onPreview(nv21: ByteArray?, camera: Camera?) { +//// faceByteArray = nv21 +// if (!isRecognition) { +// return +// } +// if (isCheckCamera) { +// L.e("检查摄像头数据") +// +// isCheckCamera = false +// +// val isBw: Boolean = BitmapUtils.isBlackWhite( +// nv21ToBitmap.nv21ToBitmap( +// nv21, +// rgbFaceRectTransformer!!.previewWidth, +// rgbFaceRectTransformer!!.previewHeight, +// 0 +// ) +// ) +// if (isBw) { +// L.e("黑白切换摄像头") +// switchCamera() +// } +// L.e("摄像头正常") +// } +// binding.dualCameraFaceRectView.clearFaceInfo() +// val facePreviewInfoList: MutableList? = +// recognizeViewModel.onPreviewFrame(nv21, true) +// if (facePreviewInfoList != null +// && rgbFaceRectTransformer != null +// ) { +// drawPreviewInfo(facePreviewInfoList) +// } +// if (facePreviewInfoList.isNullOrEmpty()) { +// binding.tvSimilarValue.text = "" +// registerHandler = RegisterCallbackHandler() +// } +// recognizeViewModel.clearLeftFace(facePreviewInfoList) +// } +// +// override fun onCameraClosed() { +// Log.d(TAG, "initRgbCamera onCameraClosed: ") +// } +// +// override fun onCameraError(e: java.lang.Exception) { +// Log.d(TAG, "initRgbCamera onCameraError: ${e.message}") +// e.printStackTrace() +// } +// +// override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { +// Log.d( +// TAG, +// "initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}" +// ) +// if (rgbFaceRectTransformer != null) { +// rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation +// } +// Log.d( +// TAG, +// "initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation" +// ) +// } +// } +// val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth +// val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight +// Log.d(TAG, "initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight") +// +// val previewConfig = recognizeViewModel.previewConfig +// rgbCameraHelper = DualCameraHelper.Builder() +// .previewViewSize(Point(measuredWidth, measuredHeight)) +//// .previewViewSize(Point(608.dp, 456.dp)) +//// .previewViewSize(Point(1024, 768)) +// .rotation(windowManager.defaultDisplay.rotation) +// .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度 +// .previewSize(recognizeViewModel.loadPreviewSize()) +//// .previewSize(Point(1080, 720)) +// .specificCameraId(previewConfig.rgbCameraId) +// .isMirror(true) +// .previewOn(binding.dualCameraTexturePreviewRgb) +// .cameraListener(cameraListener) +// .build() +// rgbCameraHelper?.run { +// init() +//// setSurfaceFrameCallback { bitmap -> +//// imageBitmap = BitmapFlipUtil.flipHorizontally(bitmap) +//// +////// binding.ivRecImage.visible() +////// binding.ivRecImage.setImageBitmap(imageBitmap) +//// } +// start() +// } +// } +// +// /** +// * 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用 +// */ +// private fun initIrCamera() { +// Log.d(TAG, "initIrCamera: livenessType = $livenessType") +// if (livenessType === LivenessType.RGB) { +// return +// } +// val irCameraListener: CameraListener = object : CameraListener { +// override fun onCameraOpened( +// camera: Camera, +// cameraId: Int, +// displayOrientation: Int, +// isMirror: Boolean +// ) { +// Log.d( +// TAG, +// "initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation" +// ) +// val previewSizeIr = camera.getParameters().getPreviewSize() +// val layoutParams = adjustPreviewViewSize( +// binding.dualCameraTexturePreviewRgb, +// binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr, +// previewSizeIr, displayOrientation, 0.25f +// ) +// +// irFaceRectTransformer = FaceRectTransformer( +// previewSizeIr.width, previewSizeIr.height, +//// layoutParams.width, layoutParams.height, +// layoutParams.width, layoutParams.height, +// displayOrientation, cameraId, isMirror, +// ConfigUtil.isDrawIrRectHorizontalMirror(context), +// ConfigUtil.isDrawIrRectVerticalMirror(context) +// ) +// +// recognizeViewModel.onIrCameraOpened(camera) +// recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer) +// } +// +// +// override fun onPreview(nv21: ByteArray?, camera: Camera?) { +// recognizeViewModel.refreshIrPreviewData(nv21) +// } +// +// override fun onCameraClosed() { +// Log.d(TAG, "initIrCamera onCameraClosed: ") +// } +// +// override fun onCameraError(e: java.lang.Exception) { +// Log.d(TAG, "initIrCamera onCameraError: ${e.message}") +// e.printStackTrace() +// } +// +// override fun onCameraConfigurationChanged( +// cameraID: Int, +// displayOrientation: Int +// ) { +// if (irFaceRectTransformer != null) { +// irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation +// } +// Log.d( +// TAG, +// "initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation" +// ) +// } +// } +// +// val previewConfig = recognizeViewModel.previewConfig +// irCameraHelper = DualCameraHelper.Builder() +// .previewViewSize( +// Point( +// binding.dualCameraTexturePreviewIr.measuredWidth, +// binding.dualCameraTexturePreviewIr.measuredHeight +// ) +// ) +//// .previewViewSize(Point(4,3)) +// .rotation(windowManager.defaultDisplay.rotation) +// .specificCameraId(previewConfig.irCameraId) +// .previewOn(binding.dualCameraTexturePreviewIr) +// .cameraListener(irCameraListener) +// .isMirror(true) +// .previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小 +// .additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度 +// .build() +// irCameraHelper?.init() +// try { +// irCameraHelper?.start() +// } catch (e: RuntimeException) { +// ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice)) +// } +// } +// +// /** +// * 绘制RGB、IR画面的实时人脸信息 +// * +// * @param facePreviewInfoList RGB画面的实时人脸信息 +// */ +// private fun drawPreviewInfo(facePreviewInfoList: MutableList) { +//// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}") +// if (rgbFaceRectTransformer != null) { +// val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( +// facePreviewInfoList, +// LivenessType.RGB, +// openRectInfoDraw +// ) +// // 识别成功 +// binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) +// } +// +// val listIsEmpty = facePreviewInfoList.isEmpty() +// val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0].trackId else null +// Log.d( +// TAG, +// "listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId" +// ) +// if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) { +// if (lastFaceTrackId != -1 && currentUserId != null) { +// Log.d(TAG, "$lastFaceTrackId 用户离开") +// lastFaceTrackId = -1 +// currentUserId = null +// } +// } +// } +// +// fun switchCamera() { +// try { +// L.e("开始切换摄像头") +// if (rgbCameraHelper != null && irCameraHelper != null) { +// rgbCameraHelper!!.stop() +// irCameraHelper!!.stop() +// rgbCameraHelper!!.switchCameraId() +// irCameraHelper!!.switchCameraId() +// rgbCameraHelper!!.start() +// irCameraHelper!!.start() +// rgbFaceRectTransformer!!.cameraId = +// if (rgbFaceRectTransformer!!.getCameraId() === 1) 0 else 1 +// // showLongToast(getString(R.string.notice_change_detect_degree)); +// L.e("切换摄像头成功") +// } else { +// L.e("切换摄像头失败") +// +// // showToast(getString(R.string.switch_camera_failed)); +// exit() +// val newIntent = Intent(this, MainActivityBak3::class.java) // 要启动的Activity +// newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) +// startActivity(newIntent) +// } +// } catch (e: java.lang.RuntimeException) { +// L.e("切换摄像头失败") +// toast(e.message + getString(R.string.camera_error_notice)) +// +// exit() +// val newIntent = Intent(this, MainActivityBak3::class.java) // 要启动的Activity +// newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) +// startActivity(newIntent) +// } +// } +// +// private fun exit() { +// finish() +// exitProcess(0) +// } +// +// override fun onDestroy() { +// countDownJob?.cancel() +// super.onDestroy() +// } +// +//// private var countDownJob: Job? = null +//// private fun countDown() { +//// countDownJob = countDownByFlow( +//// total = 3, +//// scope = lifecycleScope, +//// onStart = { +//// binding.tvCollectState.text = "采集完成(3s)" +//// }, +//// onTick = { seconds -> +//// binding.tvCollectState.text = "采集完成(${seconds}s)" +//// }, +//// onFinish = { +//// //binding.tvCollectState.text = "采集完成" +//// currentUserId = null +//// binding.layoutState.gone() +//// binding.tvFaceTip.visible() +////// binding.btnCollectFace.visible() +//// } +//// ) +//// } +// +// private val intervalExecutor by lazy { IntervalExecutor() } +//// private var faceTaskJob: Job? = null +//// +//// private var taskPageNo = 1 +//// fun startFaceTask() { +//// faceTaskJob = +//// intervalExecutor.startIntervalTaskWithInitialDelay(60 * 1000L, 30 * 1000L) { +//// val timestamp = SpTool.getLastFaceTimestamp() +//// if (timestamp == 0L) { +//// return@startIntervalTaskWithInitialDelay +//// } +//// mainViewModel.getFaceIncrementList( +//// pageNo = taskPageNo, +//// timestamp = timestamp, +//// onAllQueryFinished = { +//// taskPageNo = 1 +//// }, +//// onPageQueryFinished = { list -> +//// runOnUiThread { +//// if (list.isEmpty()) { +//// return@runOnUiThread +//// } +//// updateFaceData(list) +//// } +//// } +//// ) +//// } +//// } +// +// private fun updateFaceData(list: List) { +// Thread { +// val faceList = mutableListOf() +// try { +// list.forEach { model -> +// if (model.faceDeleted == true) { +// //删除数据 +// FaceDatabase.getInstance(this).faceDao().deleteFaceById(model.userId) +// } else { +// //保存数据 +// val faceEntity = FaceEntity( +// model.userId, +// null, +// Base64.decode(model.faceFeatureStr) +// ).also { +// it.userType = "1" +// } +// faceList.add(faceEntity) +// } +// } +// } catch (e: Exception) { +// e.printStackTrace() +// } +// try { +// if (faceList.isNotEmpty()) { +// FaceDatabase.getInstance(this).faceDao().insert(faceList) +// } +// recognizeViewModel.refreshFaceList(); +// } catch (e: Exception) { +// e.printStackTrace() +// } +// }.start() +// } +// +// private fun clearFaceData() { +// Thread { +// val faceDao = FaceDatabase.getInstance(this).faceDao() +// faceDao.deleteTempUserFaceData() +// recognizeViewModel.refreshFaceList(); +// }.start() +// } +// +// override fun onResume() { +// super.onResume() +// setupArcCamera() +// registerHandler = RegisterCallbackHandler() +// resumeCamera() +// } +// +// override fun onPause() { +//// countDownTimer?.cancel() +// super.onPause() +// pauseCamera() +// } +// +// +// private fun addUserFace(faceData: String, imageFile: File) { +// mainViewModel.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 val uploadFaceDebouncer = Debouncer(10000) +// +// private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) { +// if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) { +// loadLogInfo("collectFace:数据为空------11111------") +// return +// } +// val faceData: String? = +// com.sw.plate.utils.Base64.encode(userFaceInfo?.faceFeature?.featureData) +// if (faceData.isNullOrBlank()) { +// loadLogInfo("collectFace:数据为空------22222------") +// return +// } +// loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------") +// block(faceData, userFaceInfo!!.headBmp) +// } +// +// /** +// * 加载日志 +// * @param msg 日志内容 +// */ +// private fun loadLogInfo(msg: String) { +//// Timber.tag(TAG).d(msg) +//// LogFileUtil.saveLog(msg) +// Log.d(TAG, "loadLogInfo: $msg") +// } +// +// private val total = 30 +// private var countDownJob: Job? = null +// private fun countDown() { +////// countDownJob = countDownByFlow( +////// total = total, +////// scope = lifecycleScope, +////// onStart = { +////// binding.tvCountDown.text = "${total}s" +////// }, +////// onTick = { seconds -> +////// binding.tvCountDown.text = "${seconds}s" +////// }, +////// onFinish = { +////// } +////// ) +//// if (countDownTimer == null) { +//// initCountTime() +//// } +//// countDownDebounce.debounce { +//// countDownTimer?.start() +//// } +// } +// +// private val countDownDebounce = Debouncer(10000) +// +// // private var countDownTimer:CountDownTimer?=null +//// +//// private val totalTimeInMillis = 30 * 1000L +// fun initCountTime() { +// //countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) { +// // override fun onTick(millisUntilFinished: Long) { +// // loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}") +// // binding.tvCountDown.text = "${(millisUntilFinished / 1000).toInt() + 1}s" +// // } +//// +// // override fun onFinish() { +// // } +// //} +// } +// +//} \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/base/BaseActivity.kt b/app/src/main/java/com/sw/face/collect/base/BaseActivity.kt index b05f4d0..3ce3bd2 100644 --- a/app/src/main/java/com/sw/face/collect/base/BaseActivity.kt +++ b/app/src/main/java/com/sw/face/collect/base/BaseActivity.kt @@ -16,6 +16,8 @@ import com.sw.face.collect.databinding.DialogWaitingBinding import com.sw.face.collect.view.CustomDialog import com.sw.face.collect.R import com.sw.face.collect.model.IActivityResult +import com.sw.face.collect.utils.PermissionHelper +import kotlin.collections.set abstract class BaseActivity : AppCompatActivity() { @@ -51,6 +53,14 @@ abstract class BaseActivity : AppCompatActivity() { } + private val permissionHelpers = mutableMapOf() + + /** + * 注册PermissionHelper以便处理结果 + */ + fun registerPermissionHelper(helper: PermissionHelper) { + permissionHelpers[helper.requestCode] = helper + } /** * 显示等待提示框 diff --git a/app/src/main/java/com/sw/face/collect/ext/Common.kt b/app/src/main/java/com/sw/face/collect/ext/Common.kt index d1e66a3..d0ad511 100644 --- a/app/src/main/java/com/sw/face/collect/ext/Common.kt +++ b/app/src/main/java/com/sw/face/collect/ext/Common.kt @@ -250,3 +250,4 @@ fun String.copyText(context: Context, showToast: Boolean = true) { Toast.makeText(context, "文本已复制到剪贴板", Toast.LENGTH_SHORT).show() } } + diff --git a/app/src/main/java/com/sw/face/collect/utils/PermissionHelper.kt b/app/src/main/java/com/sw/face/collect/utils/PermissionHelper.kt new file mode 100644 index 0000000..daff411 --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/utils/PermissionHelper.kt @@ -0,0 +1,159 @@ +package com.sw.face.collect.utils + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.provider.Settings +import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import kotlin.apply +import kotlin.collections.all +import kotlin.collections.any +import kotlin.collections.forEachIndexed + +class PermissionHelper private constructor( + private val context: Context, + private val permissions: Array, + internal val requestCode: Int, + private val rationale: String? = null, + private val onGranted: (() -> Unit)? +) { + + companion object { + /** + * 创建权限请求构建器 + */ + fun with(context: Context, permissions: Array, requestCode: Int): Builder { + return Builder(context, permissions, requestCode) + } + + /** + * 检查是否已授予所有权限 + */ + fun checkPermissions(context: Context, permissions: Array): Boolean { + return permissions.all { permission -> + ContextCompat.checkSelfPermission( + context, + permission + ) == PackageManager.PERMISSION_GRANTED + } + } + + /** + * 处理权限请求结果(供BaseActivity/BaseFragment调用) + * @return 是否所有权限都已授予 + */ + fun handlePermissionResult( + context: Context, + requestCode: Int, + permissions: Array, + grantResults: IntArray, + permissionHelper: PermissionHelper? = null + ): Boolean { + if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { + permissionHelper?.onGranted?.invoke() + return true + } + + val permanentlyDeniedPermissions = mutableListOf() + permissions.forEachIndexed { index, permission -> + if (grantResults[index] != PackageManager.PERMISSION_GRANTED) { + if (!ActivityCompat.shouldShowRequestPermissionRationale( + context as Activity, + permission + ) + ) { + permanentlyDeniedPermissions.add(permission) + } + } + } + return false + } + + /** + * 跳转到应用设置页面 + */ + fun openAppSettings(context: Context) { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", context.packageName, null) + } + if (context is Activity) { + context.startActivity(intent) + } else if (context is Fragment) { + context.startActivity(intent) + } + } + } + + class Builder( + private val context: Context, + private val permissions: Array, + private val requestCode: Int + ) { + private var rationale: String? = null + private var onGranted: (() -> Unit)? = null + + /** + * 设置权限说明 + */ + fun setRationale(rationale: String): Builder { + this.rationale = rationale + return this + } + + /** + * 设置权限授予回调 + */ + fun onGranted(callback: () -> Unit): Builder { + this.onGranted = callback + return this + } + + /** + * 构建PermissionHelper实例 + */ + fun build(): PermissionHelper { + return PermissionHelper(context, permissions, requestCode, rationale, onGranted) + } + } + + /** + * 检查并请求权限 + */ + fun checkAndRequest() { + if (checkPermissions(context, permissions)) { + onGranted?.invoke() + } else { + requestPermissions() + } + } + + private fun requestPermissions() { + val activity = context as? Activity ?: return + + val shouldShowRationale = permissions.any { permission -> + ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) + } + + if (shouldShowRationale && rationale != null) { + AlertDialog.Builder(activity) + .setTitle("权限说明") + .setMessage(rationale) + .setPositiveButton("确定") { _, _ -> + doRequestPermissions(activity) + } + .setNegativeButton("取消", null) + .show() + } else { + doRequestPermissions(activity) + } + } + + private fun doRequestPermissions(activity: Activity) { + ActivityCompat.requestPermissions(activity, permissions, requestCode) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_init.xml b/app/src/main/res/layout/activity_init.xml index 5b61549..f30841a 100644 --- a/app/src/main/res/layout/activity_init.xml +++ b/app/src/main/res/layout/activity_init.xml @@ -1,5 +1,7 @@ @@ -23,7 +25,7 @@ android:orientation="horizontal"> + + + + + + + + - + - + - + diff --git a/app/src/main/res/layout/activity_main1111.xml b/app/src/main/res/layout/activity_main1111.xml new file mode 100644 index 0000000..ac6a3da --- /dev/null +++ b/app/src/main/res/layout/activity_main1111.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_bak3.xml b/app/src/main/res/layout/activity_main_bak3.xml new file mode 100644 index 0000000..e314886 --- /dev/null +++ b/app/src/main/res/layout/activity_main_bak3.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java index 110cf21..f78d7c2 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/face/model/CompareResult.java @@ -16,6 +16,10 @@ public class CompareResult { this.similarPass = similarPass; } + public boolean isSimilarPass() { + return similarPass; + } + public CompareResult(FaceEntity faceEntity, float similar) { this.faceEntity = faceEntity; this.similar = similar;