From 96e795c749ed1c6431fd5affbda67e8396d6fd78 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Tue, 3 Feb 2026 19:01:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../java/com/sw/face/collect/InitActivity.kt | 14 + .../java/com/sw/face/collect/MainActivity.kt | 638 ++++++++++++------ .../sw/face/collect/model/UserFaceModel.kt | 11 +- .../sw/face/collect/network/api/ApiService.kt | 21 + .../collect/repository/RemoteRepository.kt | 18 + .../com/sw/face/collect/utils/BitmapSaver.kt | 59 ++ .../sw/face/collect/utils/DateTimeUtils.kt | 122 ++++ .../com/sw/face/collect/utils/FileUtils.kt | 163 +++++ .../sw/face/collect/utils/SoundPoolUtil.java | 427 ++++++++++++ .../face/collect/viewmodel/MainViewModel.kt | 23 + app/src/main/res/layout/activity_init.xml | 38 ++ app/src/main/res/layout/activity_main.xml | 72 +- app/src/main/res/raw/collect_success.mp3 | Bin 0 -> 16899 bytes app/src/main/res/raw/no_plate_remind.mp3 | Bin 0 -> 19347 bytes app/src/main/res/raw/recognize_success.mp3 | Bin 0 -> 17331 bytes app/src/main/res/raw/success.wav | Bin 0 -> 76044 bytes .../plate/utils/arcface/face/FaceHelper.java | 34 +- .../arcface/face/model/CompareResult.java | 6 + .../arcface/facedb/entity/FaceEntity.java | 29 +- .../arcface/viewmodel/RecognizeViewModel.java | 21 +- 21 files changed, 1435 insertions(+), 262 deletions(-) create mode 100644 app/src/main/java/com/sw/face/collect/InitActivity.kt create mode 100644 app/src/main/java/com/sw/face/collect/utils/BitmapSaver.kt create mode 100644 app/src/main/java/com/sw/face/collect/utils/DateTimeUtils.kt create mode 100644 app/src/main/java/com/sw/face/collect/utils/FileUtils.kt create mode 100644 app/src/main/java/com/sw/face/collect/utils/SoundPoolUtil.java create mode 100644 app/src/main/res/layout/activity_init.xml create mode 100644 app/src/main/res/raw/collect_success.mp3 create mode 100644 app/src/main/res/raw/no_plate_remind.mp3 create mode 100644 app/src/main/res/raw/recognize_success.mp3 create mode 100644 app/src/main/res/raw/success.wav diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee6df36..08c8701 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,6 +51,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/InitActivity.kt b/app/src/main/java/com/sw/face/collect/InitActivity.kt new file mode 100644 index 0000000..989771f --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/InitActivity.kt @@ -0,0 +1,14 @@ +package com.sw.face.collect + +import com.sw.face.collect.base.BaseActivity +import com.sw.face.collect.databinding.ActivityInitBinding +import com.sw.face.collect.databinding.ActivityMainBinding + +class InitActivity : BaseActivity() { + override fun inflateViewBinding() = ActivityInitBinding.inflate(layoutInflater) + + override fun initialize() { + super.initialize() + } + +} \ 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 5c78165..916b3da 100644 --- a/app/src/main/java/com/sw/face/collect/MainActivity.kt +++ b/app/src/main/java/com/sw/face/collect/MainActivity.kt @@ -3,8 +3,10 @@ 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 @@ -23,17 +25,15 @@ 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.socket.LanServer -import com.sw.face.collect.socket.TcpClient 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.SpTool -import com.sw.face.collect.utils.countDownByFlow -import com.sw.face.collect.view.LanServerListenerImpl -import com.sw.face.collect.view.TcpClientListenerImpl +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 @@ -57,6 +57,8 @@ import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_R 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 @@ -65,6 +67,10 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo 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"; } // 虹软人脸配置 ⬇ @@ -101,20 +107,19 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo private var pageNo = 1 override fun initialize() { super.initialize() - mainViewModel.getUserFaceCache(pageNo = pageNo) + //mainViewModel.getUserFaceCache(pageNo = pageNo) FaceEngineUtils.activeEngine() - binding.btnCollectFace.setOnClickListener { - requestSinglePermissionResult(Manifest.permission.CAMERA) { granted -> - if (granted.not()) { - toast("没有相机权限") - return@requestSinglePermissionResult - } - collectFace() - } - } +// binding.btnCollectFace.setOnClickListener { +// requestSinglePermissionResult(Manifest.permission.CAMERA) { granted -> +// if (granted.not()) { +// toast("没有相机权限") +// return@requestSinglePermissionResult +// } +// } +// } binding.tvTitle.setOnClickListener { finish() } - addSocketListener() + //addSocketListener() binding.flFace.visible() binding.flRecognizeIr.visible() @@ -123,9 +128,24 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo binding.layoutState.gone() // resumeCamera() - startFaceTask() - } +// 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") @@ -167,152 +187,118 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo openRectInfoDraw = true } - private fun sendFaceData(faceData: String) { - // 给某个客户端发送 - val jsonObject = JSONObject().also { - it.put("type", "faceFeature") - it.put("content", faceData) - } - tcpClient?.send(jsonObject) + /** + * 改为上传人脸数据,不需要发生人脸数据到收银端 + */ + private fun sendFaceData(faceData: String, headBmp: Bitmap) { + loadLogInfo("collectFace,sendFaceData: ----------11,发送人脸数据-----------") - // TODO: 保存采集的人脸数据,暂时生成id,实际id在结算终端生成并发送到各台设备 - saveFaceInfo(faceData) - } - - private fun saveFaceInfo(faceData: String) { - Thread { - try { - val tempId = "${System.currentTimeMillis()}-123456789" - val faceEntity = FaceEntity( - tempId, - null, - Base64.decode(faceData) - ).also { - it.userType = "2" - } - FaceDatabase.getInstance(this).faceDao().insert(faceEntity) - recognizeViewModel.refreshFaceList(); - runOnUiThread { - toast("你的人脸信息已采集") - } - } catch (e: Exception) { - e.printStackTrace() - } - }.start() - } - - private var tcpClient: TcpClient? = null - - 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 + val imageFile = BitmapSaver.saveToAppFilesDir( + headBmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg" ) - - 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() - } - } - - // private var imageBitmap: Bitmap? = null - private fun collectFace() { - //toast("currentUserId=$currentUserId") - if (currentUserId != null) { - toast("您已采集过人脸信息") + loadLogInfo("collectFace,sendFaceData:${imageFile?.absolutePath}") + if (imageFile == null) { + ToastUtils.showToast("获取人脸照片失败") return } - currentUserId = null - userFaceInfo = null - recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY) - //showWaitingDialog("人脸信息中……") + uploadFaceDebouncer.debounce { + addUserFace(faceData, imageFile) + } +// // 给某个客户端发送 +// val jsonObject = JSONObject().also { +// it.put("type", "faceFeature") +// it.put("content", faceData) +// } +// tcpClient?.send(jsonObject) + } - binding.tvFaceTip.gone() - binding.btnCollectFace.gone() - binding.layoutState.visible() - binding.pbCollectLoading.visible() - binding.tvCollectState.text = "采集中......" - getFaceData { faceData -> - Log.d(TAG, "faceData: $faceData") - runOnUiThread { - //hideWaitingDialog() - //toast("人脸信息已采集") - binding.pbCollectLoading.gone() - //binding.tvCollectState.text = "采集完成" - userFaceInfo = null - countDown() + 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" } - sendFaceData(faceData) + 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 job: Job? = null - - private fun getFaceData(block: (String) -> Unit) { - runBlocking { - job = executor.startIntervalTask(5) { -// if (currentUserId != null && currentUserId!!.isNotBlank()) { -// toast("您已采集过人脸信息") -// return@startIntervalTask +// 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-> {} // } - if (userFaceInfo == null) { - return@startIntervalTask - } - val faceData = Base64.encode(userFaceInfo!!.faceFeature!!.featureData) - block(faceData) - job?.cancel() - } - } - - } +// } +// }) +// 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 @@ -339,6 +325,8 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo private var userFaceInfo: UserFaceInfo? = null private var isCheckCamera = false + private var isCollectFace = false + private val isCollecting = AtomicBoolean(false) private fun initArcViewModel() { if (DualCameraHelper.hasDualCamera()) { livenessType = LivenessType.IR @@ -351,8 +339,22 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo recognizeViewModel.prepareRegister() recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo -> - this.userFaceInfo = userFaceInfo Log.d(TAG, "initArcViewModel: userFaceInfo == null : ${userFaceInfo == null}") + if (!isCollectFace) { + return@setOnRegisterFinishedCallback + } + if (this.userFaceInfo == null && userFaceInfo != null) { + loadLogInfo("collectFace:开始采集------------") + recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE) + this.userFaceInfo = userFaceInfo + getFaceDataBlock { faceData, headBmp -> + isCollecting.set(false) + isCollectFace = false + loadLogInfo("collectFace:采集完成,准备上传------------") + sendFaceData(faceData, headBmp) + } + return@setOnRegisterFinishedCallback + } } recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? -> if (ftInitCode != ErrorInfo.MOK) { @@ -395,20 +397,20 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo 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 - } -// //未识别到,拍摄照片 - + 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 -> @@ -416,6 +418,112 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo }) } + + /** + * 识别失败重试次数 + */ + 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) @@ -545,6 +653,9 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo ) { drawPreviewInfo(facePreviewInfoList) } + if (facePreviewInfoList.isNullOrEmpty()) { + binding.tvSimilarValue.text = "" + } recognizeViewModel.clearLeftFace(facePreviewInfoList) } @@ -766,59 +877,58 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo override fun onDestroy() { countDownJob?.cancel() - faceTaskJob?.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 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 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 { @@ -854,16 +964,6 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo }.start() } -// private var dinnerTypeJob: Job? = null -// fun startDinnerTypeTask() { -// dinnerTypeJob = -// intervalExecutor.startIntervalTaskWithInitialDelay(10 * 60 * 1000L, 15 * 60 * 1000L) { -// mainViewModel.getDinnerType { -// -// } -// } -// } - private fun clearFaceData() { Thread { val faceDao = FaceDatabase.getInstance(this).faceDao() @@ -880,8 +980,96 @@ class MainActivity : BaseActivity(), ViewTreeObserver.OnGlo } 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/model/UserFaceModel.kt b/app/src/main/java/com/sw/face/collect/model/UserFaceModel.kt index ab21ba9..978bee1 100644 --- a/app/src/main/java/com/sw/face/collect/model/UserFaceModel.kt +++ b/app/src/main/java/com/sw/face/collect/model/UserFaceModel.kt @@ -21,7 +21,16 @@ data class UserFaceModel( val userId: String? = "", val faceFeatureStr: String? = "", val faceUpdateTimestamp: Long?=null, - val faceDeleted: Boolean? = false + val faceDeleted: Boolean? = false, + /** + * 会员编号 + */ + val cardNo: String, + + /** + * 是否会员 + */ + val isMember: Boolean ) // : Parcelable diff --git a/app/src/main/java/com/sw/face/collect/network/api/ApiService.kt b/app/src/main/java/com/sw/face/collect/network/api/ApiService.kt index 7a3a82a..dcd55da 100644 --- a/app/src/main/java/com/sw/face/collect/network/api/ApiService.kt +++ b/app/src/main/java/com/sw/face/collect/network/api/ApiService.kt @@ -7,6 +7,8 @@ import com.sw.face.collect.model.UserFaceModel import okhttp3.MultipartBody import okhttp3.RequestBody import retrofit2.http.Body +import retrofit2.http.Field +import retrofit2.http.FormUrlEncoded import retrofit2.http.GET import retrofit2.http.Multipart import retrofit2.http.POST @@ -42,4 +44,23 @@ interface ApiService { suspend fun getDinnerType( @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getRegionRule", ): ApiResponse + + @Multipart + @POST + suspend fun uploadImage( + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/upload", + @Part file: MultipartBody.Part, + ): ApiResponse + + + /** + * 添加人脸数据 + */ + @POST + @FormUrlEncoded + suspend fun addUserFace( + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/addUserByFace", + @Field("featureChar") faceData:String, + @Field("url") imageUr:String + ): ApiResponse } \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/repository/RemoteRepository.kt b/app/src/main/java/com/sw/face/collect/repository/RemoteRepository.kt index e31bd0d..74f5999 100644 --- a/app/src/main/java/com/sw/face/collect/repository/RemoteRepository.kt +++ b/app/src/main/java/com/sw/face/collect/repository/RemoteRepository.kt @@ -5,6 +5,8 @@ import com.sw.face.collect.model.ApiResponse import com.sw.face.collect.model.DinnerType import com.sw.face.collect.model.UserFaceModel import com.sw.face.collect.network.api.ApiService +import com.sw.face.collect.utils.FileUtils +import java.io.File /** * 远程数据处理 @@ -57,4 +59,20 @@ class RemoteRepository constructor( } } + + suspend fun uploadImage(file: File): ApiResponse { + return safeApiCall { + val part = FileUtils.genRequestPart(file) + if (part == null) { + ApiResponse(code = "-1", msg = "解析图片失败") + } else { + apiService.uploadImage(file = part) + } + } + } + + suspend fun addUserFace(faceData: String, imageUr:String): ApiResponse { + return safeApiCall { apiService.addUserFace(faceData = faceData, imageUr = imageUr) } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/utils/BitmapSaver.kt b/app/src/main/java/com/sw/face/collect/utils/BitmapSaver.kt new file mode 100644 index 0000000..47fadac --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/utils/BitmapSaver.kt @@ -0,0 +1,59 @@ +package com.sw.face.collect.utils +import android.content.Context +import android.graphics.Bitmap +import android.os.Environment +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import kotlin.io.use + +object BitmapSaver { + // 保存到公共目录(需WRITE_EXTERNAL_STORAGE权限) + fun saveToPublicDirectory( + bitmap: Bitmap, + folderName: String = Environment.DIRECTORY_PICTURES, + fileName: String, + format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG, + quality: Int = 100 + ): File? { + val dir = Environment.getExternalStoragePublicDirectory(folderName) + if (!dir.exists()) dir.mkdirs() + + return saveBitmap(bitmap, File(dir, fileName), format, quality) + } + + // 保存到应用私有目录(无需权限) + fun saveToAppFilesDir( + bitmap: Bitmap, + context: Context, + fileName: String, + format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, + quality: Int = 100 + ): File? { + //val dir = context.getExternalFilesDir(null) + val dir = context.cacheDir + val cropFile = File(dir, "crop") + if (cropFile.exists().not()) { + cropFile.mkdirs() + } + return saveBitmap(bitmap, File(cropFile, fileName), format, quality) + } + + private fun saveBitmap( + bitmap: Bitmap, + outputFile: File, + format: Bitmap.CompressFormat, + quality: Int + ): File? { + return try { + FileOutputStream(outputFile).use { fos -> + bitmap.compress(format, quality, fos) + fos.flush() + } + outputFile + } catch (e: IOException) { + e.printStackTrace() + null + } + } +} diff --git a/app/src/main/java/com/sw/face/collect/utils/DateTimeUtils.kt b/app/src/main/java/com/sw/face/collect/utils/DateTimeUtils.kt new file mode 100644 index 0000000..30cd217 --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/utils/DateTimeUtils.kt @@ -0,0 +1,122 @@ +package com.sw.inbound.utils + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.flow +//import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import kotlin.math.abs +import kotlin.to + +/** + * 时间格式化工具类 + */ +object DateTimeUtils { + + /** + * 获取完整中文日期格式(示例:2025年6月11日 星期三) + */ + fun getChineseDateString(date: Date = Date()): String { + return SimpleDateFormat("yyyy年M月d日 EEEE", Locale.CHINA).format(date) + } + + /** + * 获取标准时间格式 + */ + fun getDateTimeString(date: Date = Date()): String { + return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(date) + } + + /** + * 获取带时间的完整中文格式(示例:2025年6月11日 星期三 14:30) + */ + fun getChineseDateTimeString(date: Date = Date()): String { + return SimpleDateFormat("yyyy年M月d日 EEEE HH:mm:ss", Locale.CHINA).format(date) + } + + // 使用线程安全的日期格式化(避免 SimpleDateFormat 的线程安全问题) + private val dateFormat by lazy { + SimpleDateFormat("yyyy年M月d日 EEEE", Locale.CHINA) + } + private val timeFormat by lazy { + SimpleDateFormat("HH:mm:ss", Locale.CHINA) + } + + fun getChineseDateTimePair(date: Date = Date()): Pair { + return dateFormat.format(date) to timeFormat.format(date) + } + +// /** +// * 实时时间流(每秒更新) +// * @param intervalMillis 更新间隔(默认1秒) +// */ +// fun realTimeChineseDateFlow(intervalMillis: Long = 1000) = flow { +// while (true) { +// FlowCollector.emit(getChineseDateTimePair()) +// delay(intervalMillis) +// } +// } + + /** + * 解析日期时间字符串 + * @param timeString 格式为 "yyyy-MM-dd HH:mm:ss" 的字符串 + * @return Date 对象,解析失败返回 null + */ + fun parseDateTime(timeString: String?): Date? { + return try { + if (timeString == null) return null + val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + format.parse(timeString) + } catch (e: Exception) { +// e.printStackTrace() +// Timber.e(e.message) + null + } + } + + /** + * 判断给定时间是否距离当前时间超过72小时 + * @param timeInMillis 时间戳(毫秒) + * @return true 表示超过72小时,false 表示未超过 + */ + fun isMoreThanHoursFromNow(timeInMillis: Long): Boolean { + val currentTime = System.currentTimeMillis() + val timeDifference = currentTime - timeInMillis + val hoursDifference = timeDifference / (1000 * 60 * 60) // 毫秒转小时 + return hoursDifference >= 72 + } + + /** + * 获取时间间隔描述 + */ + fun getTimeAgo(date: Date?): String { + if (date == null) return "未知时间" + + val now = Date() + val diffMillis = now.time - date.time + + // 如果是未来时间 + if (diffMillis < 0) { + val futureHours = abs(diffMillis) / (1000 * 60 * 60) + return if (futureHours < 24) { + "未来 $futureHours 小时" + } else { + val days = futureHours / 24 + "未来 $days 天" + } + } + + // 过去时间 + val hours = diffMillis / (1000 * 60 * 60) + return when { + hours < 1 -> "刚刚" + hours < 24 -> "${hours}小时前" + else -> { + val days = hours / 24 + "${days}天前" + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/utils/FileUtils.kt b/app/src/main/java/com/sw/face/collect/utils/FileUtils.kt new file mode 100644 index 0000000..5186783 --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/utils/FileUtils.kt @@ -0,0 +1,163 @@ +package com.sw.face.collect.utils + +import android.content.ContentUris +import android.content.Context +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import androidx.annotation.RequiresApi +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +// import Timber.log.Timber +import java.io.File +import kotlin.io.copyTo +import kotlin.io.outputStream +import kotlin.io.use +import kotlin.text.equals + +object FileUtils { + /** + * 从Uri获取File + * example: file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg + */ + private fun getFileFromUri(context: Context, uri: Uri): File? { + //Timber.d("getFileFromUri uri = ${uri.scheme}") + return when (uri.scheme) { + "file" -> File(uri.path ?: return null) + "content" -> { + try { + val inputStream = context.contentResolver.openInputStream(uri) ?: return null + val cacheDir = context.cacheDir + val file = File.createTempFile( + "upload_${System.currentTimeMillis()}", + ".jpg", + cacheDir + ) + file.outputStream().use { output -> + inputStream.copyTo(output) + } + file + } catch (e: Exception) { + //Timber.e(e) + null + } + } + + else -> null + } + } + + /** + * 通过uri生成http请求体 + */ + fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? { + //Timber.d("genRequestPart imageUri = $imageUri") + // 1. 从Uri获取文件 + val file = getFileFromUri(context, imageUri) + return genRequestPart(file) + } + fun genRequestPart(file: File?): MultipartBody.Part? { + if (file == null) { + //Timber.e("getFileFromUri file is null") + return null + } + // 2. 创建请求体 + val requestFile = file + .asRequestBody("application/octet-stream".toMediaTypeOrNull()) + val imagePart = MultipartBody.Part.createFormData( + "file", + file.name, + requestFile + ) + return imagePart + } + + /** + * 通过Uri删除文件 + * @param context 上下文 + * @param uri 文件Uri + * @return Boolean 是否删除成功 + */ + fun deleteFileWithUri(context: Context, uri: Uri): Boolean { + //Timber.d("deleteFileWithUri uri = ${uri.scheme}") + return when { + // 1. 处理 content:// 类型的Uri (MediaStore) + uri.scheme.equals("content", ignoreCase = true) -> { + deleteContentUriFile(context, uri) + } + // 2. 处理 file:// 类型的Uri + uri.scheme.equals("file", ignoreCase = true) -> { + deleteFileUriFile(uri) + } + // 3. 其他情况尝试直接解析路径 + else -> { + deleteFileFromPath(uri.path ?: return false) + } + } + } + + // 删除Content Uri文件 + private fun deleteContentUriFile(context: Context, uri: Uri): Boolean { + //Timber.d("deleteContentUriFile uri = ${uri.scheme}") + return try { + context.contentResolver.delete(uri, null, null) > 0 + } catch (e: SecurityException) { + // Android 10+需要特殊处理 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + deleteMediaStoreFile(context, uri) + } else { + false + } + } catch (e: Exception) { + //Timber.e(e) + false + } + } + + // Android 10+删除MediaStore文件 + @RequiresApi(Build.VERSION_CODES.Q) + private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean { + //Timber.d("deleteMediaStoreFile uri = ${uri.scheme}") + val contentResolver = context.contentResolver + val projection = arrayOf(MediaStore.MediaColumns._ID) + + return try { + contentResolver.query(uri, projection, null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val id = + cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)) + val contentUri = ContentUris.withAppendedId(uri, id) + contentResolver.delete(contentUri, null, null) > 0 + } else { + false + } + } ?: false + } catch (e: Exception) { + //Timber.e(e) + false + } + } + + // 删除File Uri文件 + private fun deleteFileUriFile(uri: Uri): Boolean { + //Timber.d("deleteFileUriFile uri = $uri") + return try { + File(uri.path ?: return false).delete() + } catch (e: Exception) { + //Timber.e(e) + false + } + } + + // 直接通过路径删除文件 + private fun deleteFileFromPath(path: String): Boolean { + //Timber.d("deleteFileFromPath path = $path") + return try { + File(path).delete() + } catch (e: Exception) { + //Timber.e(e) + false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/face/collect/utils/SoundPoolUtil.java b/app/src/main/java/com/sw/face/collect/utils/SoundPoolUtil.java new file mode 100644 index 0000000..5c6d821 --- /dev/null +++ b/app/src/main/java/com/sw/face/collect/utils/SoundPoolUtil.java @@ -0,0 +1,427 @@ +package com.sw.face.collect.utils; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.SoundPool; +import android.os.Build; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class SoundPoolUtil { + + private static final String TAG = SoundPoolUtil.class.getSimpleName(); + private static SoundPoolUtil mSound; + private SoundPool mSoundPool; + private boolean isLoadC = false; + private Map idCache; + private List sidCache; + + public SoundPoolUtil() { + idCache = new HashMap<>(); + sidCache = new ArrayList<>(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + AudioAttributes aab = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + mSoundPool = new SoundPool.Builder() + .setMaxStreams(10) + .setAudioAttributes(aab) + .build(); + } else { + mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 8); + } +// mSoundPool = new SoundPool(60, AudioManager.USE_DEFAULT_STREAM_TYPE, 7); + mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener()); + } + + public static SoundPoolUtil getInstance() { + + synchronized (SoundPoolUtil.class) { + if (mSound == null) { + mSound = new SoundPoolUtil(); + } + } + return mSound; + } + + private int loadCompleteSize = 0; + private LoadCompletion completionListener; + + public void setCompletionListener(LoadCompletion listener) { + this.completionListener = listener; + } + + private class MyOnLoadCompleteListener implements SoundPool.OnLoadCompleteListener { + + @Override + public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { + loadCompleteSize++; + + //L.e("loadSize" + loadSize + "===" + loadCompleteSize); + if (loadSize == loadCompleteSize) { + isLoadC = true; + if (completionListener != null) { + completionListener.onCompletion(); + } + } + } + } + + + /** + * 加载指定资源 + * + * @param name + * @param path + */ + public void loadR(String name, String path) { + if (checkSoundPool()) { + if (!idCache.containsKey(name)) { + idCache.put(name, mSoundPool.load(path, 1)); + } + } + } + + private int loadSize = 0; + + /** + * 加载指定路径列表的资源 + * + * @param map + */ + public void loadR(Map map) { + loadSize = map.size(); + Set> entries = map.entrySet(); + for (Map.Entry entry : entries) { + String key = entry.getKey(); + if (checkSoundPool()) { + if (!idCache.containsKey(key)) { + idCache.put(key, mSoundPool.load(entry.getValue(), 1)); + } + } + } + } + + /** + * 加载指定AssetFileDescriptor的资源 + * + * @param name + * @param afd + */ + public void loadRF(String name, AssetFileDescriptor afd) { + if (checkSoundPool()) { + if (!idCache.containsKey(name)) { + idCache.put(name, mSoundPool.load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), 1)); + } + } + } + + /** + * 加载指定AssetFileDescriptor列表的资源 + * + * @param map + */ + public void loadRF(Map map) { + Set> entries = map.entrySet(); + for (Map.Entry entry : entries) { + String key = entry.getKey(); + if (checkSoundPool()) { + if (!idCache.containsKey(key)) { + idCache.put(key, mSoundPool.load(entry.getValue().getFileDescriptor(), entry.getValue().getStartOffset(), entry.getValue().getLength(), 1)); + } + } + } + } + + /** + * 加载指定列表资源 + * + * @param context + * @param map + */ + public void loadR(Context context, Map map) { + loadSize = map.size(); + Set> entries = map.entrySet(); + for (Map.Entry entry : entries) { + String key = entry.getKey(); + if (checkSoundPool()) { + + if (!idCache.containsKey(key)) { + idCache.put(key, mSoundPool.load(context, entry.getValue(), 1)); + } + } + } + } + + /** + * 加载单个音频 + * + * @param context + * @param name + * @param res + */ + public void loadR(Context context, String name, int res) { + if (checkSoundPool()) { + if (!idCache.containsKey(name)) { + idCache.put(name, mSoundPool.load(context, res, 1)); + } + } + } + + /** + * 播放指定音频,并返用于停止、暂停、恢复的StreamId + * + * @param name + * @param times + * @return + */ + public int play(String name, int times) { + //L.e(String.format("play %s times=%s", name, times)); + return this.play(name, 1, 1, 1, times, 1); + } + + /** + * 播放指定音频,并指定播放次数和频率 + * + * @param name + * @param times + * @param rate + * @return + */ + public int play(String name, int times, int rate) { + return this.play(name, 1, 1, 1, times, rate); + } + + /** + * 播放指定音频,并指定优先级和播放频率 + * + * @param name + * @param property + * @param times + * @param rate + * @return + */ + public int play(String name, int property, int times, int rate) { + return this.play(name, 1, 1, property, times, rate); + } + + /** + * 播放指定音频,并指定左右声道、优先级、播放次数、播放频率 + * + * @param name + * @param leftVolume + * @param rightVolume + * @param property + * @param times + * @param rate + * @return + */ + public int play(String name, float leftVolume, float rightVolume, int property, int times, int rate) { + int streamId = -1; + if (checkSoundPool()) { + //L.d(TAG, "play: " + name); + if (idCache.containsKey(name) && isLoadC) { + //L.d(TAG, "name:" + idCache.get(name)); + streamId = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate); + //L.d(TAG, "streadmId:" + streamId); + sidCache.add(streamId); + } + } + return streamId; + } + + /** + * 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表 + * + * @param names + * @param times + * @return + */ + public List play(List names, int times) { + + return this.play(names, 1, 1, 1, times, 1); + } + + /** + * 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定次数和频率 + * + * @param names + * @param times + * @param rate + * @return + */ + public List play(List names, int times, int rate) { + + return this.play(names, 1, 1, 1, times, rate); + } + + /** + * 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定所有参数 + * + * @param names + * @param leftVolume + * @param rightVolume + * @param property + * @param times + * @param rate + * @return + */ + public List play(List names, int leftVolume, int rightVolume, int property, int times, int rate) { + List streamIds = new ArrayList<>(); + if (checkSoundPool()) { + for (String name : names) { + if (idCache.containsKey(name) && isLoadC) { + int a = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate); + streamIds.add(a); + sidCache.add(a); + } + } + } + return streamIds; + } + + /** + * 停止指定id音频 + */ + public void stop(int r) { + if (checkSoundPool()) { + mSoundPool.stop(r); + } + } + + /** + * 停止指定列表音频 + */ + public void stopAll() { + if (checkSoundPool()) { + for (int r : sidCache) { + mSoundPool.stop(r); + } + } + } + + /** + * 暂停指定音效 + * + * @param r + */ + public void pause(int r) { + if (checkSoundPool()) { + mSoundPool.pause(r); + } + } + + /** + * 暂停指定列表音频 + * + * @param list + */ + public void pause(List list) { + if (checkSoundPool()) { + for (int r : list) { + mSoundPool.pause(r); + } + } + } + + /** + * 暂停所有音效 + */ + public void pauseAll() { + mSoundPool.autoPause(); + } + + /** + * 恢复指定音频播放 + * + * @param r + */ + public void resume(int r) { + if (checkSoundPool()) { + mSoundPool.resume(r); + } + } + + /** + * 恢复指定列表的音频 + * + * @param list + */ + public void resume(List list) { + if (checkSoundPool()) { + for (int r : list) { + mSoundPool.resume(r); + } + } + } + + /** + * 恢复所有暂停的音频 + */ + public void resumeAll() { + if (checkSoundPool()) { + mSoundPool.autoResume(); + } + } + + /** + * 卸载指定音频 + * + * @param name + */ + public void unLoad(String name) { + if (checkSoundPool()) { + if (idCache.containsKey(name)) { + mSoundPool.unload(idCache.get(name)); + idCache.remove(name); + } + } + } + + /** + * 卸载指定列表的音频 + * + * @param names + */ + public void unLoad(List names) { + if (checkSoundPool()) { + for (String name : names) { + if (idCache.containsKey(name)) { + mSoundPool.unload(idCache.get(name)); + idCache.remove(name); + } + } + } + } + + + /** + * 释放所有资源,如果想继续播放,需要重新加载资源 + */ + public void release() { + if (checkSoundPool()) { + mSound = null; + mSoundPool.release(); + idCache.clear(); + } + } + + private boolean checkSoundPool() { + if (mSoundPool != null) { + return true; + } + return false; + } + + public interface LoadCompletion { + void onCompletion(); + } +} + diff --git a/app/src/main/java/com/sw/face/collect/viewmodel/MainViewModel.kt b/app/src/main/java/com/sw/face/collect/viewmodel/MainViewModel.kt index 1127e43..e279442 100644 --- a/app/src/main/java/com/sw/face/collect/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/sw/face/collect/viewmodel/MainViewModel.kt @@ -17,6 +17,7 @@ import com.sw.plate.utils.arcface.facedb.entity.FaceEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.File class MainViewModel() : ViewModel() { companion object { @@ -141,4 +142,26 @@ class MainViewModel() : ViewModel() { } } + + /** + * 添加人脸数据 + */ + fun addUserFace(faceData: String, imageFile: File, block: (Boolean, UserFaceModel?) -> Unit) { + viewModelScope.launch { + val imageResp = repository.uploadImage(imageFile) + val imageUrl = imageResp.data?:"" + if (imageUrl.isBlank()) { + block(false, null) + return@launch + } + val faceResp = repository.addUserFace(faceData, imageUrl) + val status = parseResponse(faceResp) + if (status.not()) { + block(false, null) + return@launch + } + block(true, faceResp.data) + } + } + } \ 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 new file mode 100644 index 0000000..5b61549 --- /dev/null +++ b/app/src/main/res/layout/activity_init.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e83bfc3..2b7cfa9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -65,16 +65,28 @@ - + + + + + - + + + + + + - + + + + + + + + + + - + diff --git a/app/src/main/res/raw/collect_success.mp3 b/app/src/main/res/raw/collect_success.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7a81891d223095ec7b407ba61697f16c138a2fc4 GIT binary patch literal 16899 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 aGM0Zg{wJS*NAX`){%^?Vzl8rcHU1Bs9a-4` literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/no_plate_remind.mp3 b/app/src/main/res/raw/no_plate_remind.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..47e6d1815da84f348a4f590364902b794509ab11 GIT binary patch literal 19347 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 I{~HVb57)_arT_o{ literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/recognize_success.mp3 b/app/src/main/res/raw/recognize_success.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fcca2e5cafeb9b6a5b0cc458f0a232f734cd04be GIT binary patch literal 17331 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/success.wav b/app/src/main/res/raw/success.wav new file mode 100644 index 0000000000000000000000000000000000000000..d89f5c3f6107898f5ce757f63afa35e5942c571c GIT binary patch literal 76044 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

aJgR^#IwSIMKwQ6nPc8x0OK7D6KpRqS|xN?$! zOk$>q4MBbMop4fAb#%$O{tMuRKR6eW)AjWC8A!xBSlXgDfDt|N})GQhY{Co=9Xb6V|5AgYLY&crf~JR5zw z{bccR-=oEckVbL8?p`tIXZmL8#^QSat%kKS&`Z`TX|;Sc;b!@*l{+2krERGede*$( zder3{^KD^avX&}g=puT#P5d$8xG3XN?PZH}P=>~q%N?=t@_2bq?6^#OMI|*!%;ISg z=|Z7UbygkS$14O5&SiFXL|%9^Lqy*R<^`n(6cY^CWQ-F0!klqsI^vIcR=Q|X8wpMQQ`M7YGh%(xN{dYLM3m&aXYUNyzG#LmiMu1GF3 zFG(-my_c}OpHBXEW^hUZ~v&<yowo54pghkfVlF_B)b|iuIMXgqw3W8kH;8Q?FNE-@2Z1L%cS)(W9E*%Gwd? zQ;l;6n&Vb?A!NYk2j+%&n0=A`XPO1}^D!4CmntteU&)SLh!x7=tCFj$a=F|R+aep0 z4oL`?;(_;m6;yQ)xfmCEb#>$%q(m4ut)>#R-F7oP1& zt?FsYUfdy2!+2MqN{0cR)&fl(dON^K4-BPqdneIK~;u5|Z0I*${rF$a)o z+tB~COIPx!KvQ#nQC*@EZ?ZRRw^G)MS9Qw#>kZ)Ag{rVB)|I@qk=vDbP4`$2YjsV} z()X2C@GeABU@=J*lpi`CKF(R-WrH5_MU&zsNsF}cicYo>E0yPhBir&z`?c_;&T@6Z4taSK}v75L&j21m^C&S9My-jhFTeZtf# zH7WPG>e0J(cPO`~)>NzX8%@`@ujy9?R>;?uuQ{$y-%P!&->kW(2cJRWXE`Q{RqQZ$ zff3L9B-HJwZz89?k>4I2HX)}~pwKdV;X z&ADS;cYt?C9Ozq-f^n^4Wo%{nn&tY+Dtb$IXI$O;pj$inl)pD>$+7Rc0m%l>2+XCG zgbCT%Tr@i2Tv^PrXj|MU5lGvw%*v`_=VC3f6S0}G2{Ou+!OJ_BU@>2m6hjj-1^v;o zGn*Vogo)`0U89u+*#q(kYPim?@={z?_6cycG-K@Ft=3iTRByY~$X(H$l6A&f{A#;W zt;oDKwo*Lqr}Q%n`D_+YNX4T>8Xs(jFOuSqp>r)a(NX2%V_vPnU{R^( zwc6aLwMN^)@GrD93ZW!>cjX`8e|%8?FV zUXoN@Din{4gcs7!m!4bWv!e%qqo^sOjA@}~2JZxl!BrZ9#v?rE__5<~)I=~Q>lYty zYufMQ)K#1D8~j_sHN}nL8+FPa<=_p=jpntnTckU4cZ=>-JZOHDt~VG;%`rzCjvTKU z>clsbalzcs%J4OI1D7A2AxIJq#>hp(7q`T;OIw#Tmt>OpOX5pa;_-_UqNEF^^RO^a zV2@roBZz8c&xQ9e210VEa9}oZ6>IU!5vGe{PqC(&Wk%lapf2Z8wkG}lin>>|yvew; zd7F8=e|>75dmFdWerM>e{fnh7Skw8a;7P)>oV~^a{ZWNO<4O0AW7(v1N&?LqnjK!u z=0$a!*@&J#8-K1rSb9D`#ubxtA?HHe1$9h%OyT()VfH!WSr(tmivuxIMUcXC7!x7e z)X_i*X%1I~$v{^)nzo*co=xlHbaC3&omEZY1O9gUef^elt8Qy!Yw^Br zyBK(?b|2N~*7eQLT8;L7-eI#%;@EQM`*ct-PEX7VOrVNFC=3Nt5Sq@(gDb-0G;ENQkWs-wsoRriE-$^C=AhEMgB12M_tzkWLT5HG9%> zTyoTSSa~qA&os>%1;%6hfl-;_gZBHkjE`$#*$Ju+#)Y)gXG4{Y!LTysCKDH)9!?8iXI3zEVdStF z1{^vQl0{pfRt9y05f>XVh0uT-g^du!-{@Nfqm!GiN~gp@Katx-$MrVlQSy<_+H5Vd z%B&gID(k$Ja#VLjwRPLFj*;UH`}|3lquJT)>I0*vq2Y#tOF#4|mUN0*kfKh?BO9;jc zQh}2z52zF>!5Dh|iy1h!!T&m_2Kb%I0CB7UJf90lW2xZRuN3GE*gzFQ0lEnI1y#@P zcK`~U1&oUufp6;6XQl@8sh{lS?dh`9>6H4Xd$HCjbmR z3o=rfAZk2F1=^UzZ2NObNny#%5n5BQhBW6@9ix^d6LoLUqB@p(={R2CM4*ctNA_Ti zI1^B|tOT+t+r$N5oW15?=6T*O`V2A+AB}nS*g{eXxi7Gav<8j4aVK3?>S68C%n9Vh zVTI(eAOm%SiVN(+1D~9`(_Vi}IT;07`4uRiFhCLnvI4hA!#EMVjBq`Du0^-P-Q~l9 ze3=R;1#-a%C^G^TsxCH*^TkuV$&f8nW=k^X@AHmN4GE6zNf)@Ks9;%D$n6ft!RW|1E@ zA2wSDj_M)suRQeIa0*S9-NIUHL8KJ0+#e@2>nc@_uo8=bPiP#tXY=hqyVBE#aME%8+WB zn&zSvQ7OSKAqb65V+9unwFT_p8&IXA$igrzJea;c4P?<9llh~&zi9uct|#fovmeZV zF!vMA&!`h4*BW%-c=L18#3i}^%m01<_dWl|_>Y>u5q-`6x=OqnB@8tO4U>!U47dO- z_1K(Zr`=KH>V=X5E11e?!DV~gTms>BsciGy4AX#f9JT4vRb$H51?#ka%>0YW-oAeo z{in+S)11@M@k!&_wkw5^wu?eAxDcH z==(*Bl79?EH)d!5^Pk)Df1md~kW;z5$toamkjxXZU1(R^d+lu7{Gq`rKV~~qF@jL$ zIp%Ab?s%stn+Jz-NED{={MPG9*(1e^f1C8(&4R+TCSYe< zG&iXfORDMaUn|Eue%vV=9sg|d zAy3oxm}D$I;ya7ITAehwPvU~W2C8pE>ccdqe* zG4@#3c+=?6pr@z%FJwndbADf;qSdJvBj5RQYqdqmaceZ|@vbiCzV%-JL*s6RCEJmJ zsQea;0$&(7$)E~aug<2Wep_6=T~Yf#X7gz&rTh%IN8hC!`Q6-T?k|Q06F*KG%KmxB zPlr2Ye>o>7dIG zF)D1kI`od<+e?49T_yZGN1^PU;j=Y<+M|ud+A;aS?1v*Cnts;x3+d0SA9A~lKlpPj zyML@%?st1)jWu6xE&07fd6LEsrRld!{pfm;IUWZpxk+v68bHg%Rp-Nw|{RdY*SB`>Xcig1=w=2SUxj zcX?mYa3`H{8+ntopJxA1{e!OmtN%dJwcR!K*{4Okdz;pJnih0ex)s!sM02)ZK!e^6BrTR$2e1^X+m@%}Ljq@{{Tpygp3xzz-T) zn|_qjIsLGE8BXL@>mPjTOOEB{9F^+L&HV7fuR+&9er(mRU#gS6JUAB;42{b+7r>eJ#! zv-JG5u7CQkmTKzelAD6Yb-Ja6$IA;*NpNwVu`Crq8mTYnd^bUv7tA4qZw*nOB%T)^^Tzk%lFU zHti^;hnEm9Pc3>!{m%S5y>A!4)sf!xJ~u!A+Z*|wtf@Fx*xWG~=l*T;hutrTqVNxL zf5`4K_oaQr{&2U?@p0i4|Hk$qfwP;D*!W*<^}OE_#V|n&)%{gJn){&Ty|Y+#KM_N_kn|1qUyjjy#l{;{3Ryezd*;tGdl>!JUnkt)?6*=H zL~SYg-)PSfznA%q>UT|V7o~Kh=caYP%}=h2=kkY0*+@cMbQ*>R&mV`Yz!w@1=t8l3&XVRBNl}vIgfr-1x{eK65=~uQo8| zqUtLP@Ac&874*E{m7e^SmgMP#YWb$TLRNLYBphTZoL23sa(*u3*9(KzUh+>WezNe> z?18Dl_D`grT0XaLZXB9uW0xi}n=AXl_wdm7r>d)R>>@I*^f9`WIX?R_bF}7D^j5>( z1gS@;N_4#2^d2d%_&p!}qfPd9bm#R`;E%uZ(f$-MsDkMd}1&)b#7lg>KcP z#U9vwe(Vi>`oMchx#Vv+(re$Df2;bnZrSE#L5wM~j+THE*|d+yw^^SL{dROD<7X|s z10OE+rhZgC%$*uqMD7fjmjVUn?J4VjUHcDXwXOfy_&*3)otJh3&HIiG?mT(2{@*FT zN3SD09WDuD>q2H?;@66DR(a1 z`en}-Ipz*LnbR$qPi#pm`C4bj?pG_*j42&&bX*<1LKme($wQR*ddKur#TViL{s zcMnbejQ3INPrLgi|F$&U_qpKSlr1H=BW5Q>SkPBC_jmDSyl)S`-EyvjRJ>n*7x%eq zw))e>|7g6Wdz|Am2KNgp<*K)HGO9AN(iLCbNm+YcD%VQ&F>O&fVKqUce$io*K7DK8 zy8iba6T)9Aepb>a_(=7Ou3z)!`)^U7cjIQb)?CR5r@ z$9mk7VS3_|m0#5SY`uSeXmp%8mvpoFX)QV#J|iWiJMy{<8HM`Xp|5ORbc9k7{_etN z({=KnGOwkr7e53F4``XrJByQ6zb<>r@b>82_P3@J+h5aO5s9I5H5_56ip2BfSjA5T z_n0?&=SL?cqgg`>KPL>2k0;Jrt`oG4&Y57(d0rC#>m%=vBX<_HFxDAviXY;~iiXqR@x^N&#CBf12*O9e97)!nPOtNF4_NtQ@)4#!Jl^BMgVDK5qGBV1}Td@C>)b;V%uab7ngXHuuM{qXV|i6n9)x@$=Yo* z(TV<}wWZ}kH6|XId%1&iAhtMfqK-a!-tTm#K+P{cj8@<-@ zdD~}QpIfdE+-ZK;y0_v=Bf98okvq}oSsQPN9TTb!;QBXCxQA;d`d*Hy+OlY8B7Imo zWt}k;NsS&jw|O2p-+VqFc!BbxrQG63Yxqp)c(9sm!0Ns7*m!Zef3rh_aS)6IhNh~vQ(;b~C> zKL1=XV9j=&t3Jz#?&NkwF0n?!==5T0cK`>g^cEkl?j`H2_v`PXw~9eFGUnFOrfIwI zY20CqHwIr9WC|?_uVwN=`vD=A2T9zfV~(}oQgc{ilQ^ZmA#4|UGK9pciJFWaI~ylR z6~v!S1lbBk)G9ljRS{M|@1z2vGn@|G`3ZZ?x`YRUFETga_0jdQJIVKQA2|&DM^j#) zfey^3WzgjzJZf*i2sXht>#RRUtt-|d+ssLcM+!9)Qi8Y)3A>n^8g1t%pDp2QcuU+) zP6^u_-V??H`6NeR0#ON}F0Hl2IHeoVB;RA+UAW`f6sgq@YM*47G{`RBq%73ke$t zC4>|N=}9*1m@nSZZAp4gc#{7xbu0f1lPXQUd4J{+?ODZv_N2(C1Q~+Lz|6p%01u%G zZt(HlsuT3M^LXAq>YVcqK&6Cr3MZ5qu@Ke5i{tao=J6AG)~HI58=DK$(JN?EL7M>$ z_zh^tQ+YgWZZ!<)1P>+m%kI_POS|9tu=>e_vB%1DZJ_nIRMK=naX<;t0o3E=-Y$^S zud>hDdmS>54K2czkqIFw;V>uZOmTDye-LOHG+?Tzj

JqOi|+SkM4A0Y3s93#&i}p6EU0K$bfe9U2!3NXgAO8Cgv03o}HxI417=8R40} zsFBF#2qiO(;R)FZZV6J5+lbv*4Vvh#J}$RR@6GNG=wR*qj$)@;JNcy5kYGlRI-J|c z7_<#Lumjj$pwp{BX5G8a1m}>`mW-cu%8?>Tg<}ze- zdvG6M(XJ8+xLv=>Q*<(BWt%ge*FIJ1=#P7~6OU8$QUhyIFCR-&4@4N=NSA$vXC&d8xNXj?&6@;p#L z4na!qpd-V^0ed4g%s*8>vFJLVfTQ$`VOq8n98c= zKi^pOEc99#SB6E}=cy+Gy4rVFU2OK1KBO z-9ZE2WH}(&Ivr!40pGM=4l1X#KxuvxaIjT+ao&7yB~p&2Vpeg40BsPT-Vj#7nqo_V zMrl4Gm1PgB4uwO;scDqbfMJ3I+XCewHLh)Y+|kOxgo$R98aU5%yJOEro==%L7S|EO zvFlz(P;?xUV7j4Re>2#dB7}?-A_c$!*o#4M9H2!?3>jopgl9$=BibUCS()L2Fg<;m zRzo!h#s^dq8nF;WNAg@Y``A&VWoy5EPyO6tSTLkKHyFG3do8m^$tP>Be($QU575?h zP&4o^7W+5QN>Ir~1V%pd;CTEjDVMSste}^KNx~iB#_*hQ9y6aIr<;S@gXYL{L?vz- zBSl9&9nNN;vLGFfn}<#Ddy~d?BVljMa$&95?LMVYgNK=DP*(!1su@qO2VW2~vY3GjJd?Y$t zHxB^~*Y5Mp=L1HGsmh#l*kM~Z*>JUcO+KB!9ILl|U1z2af=!O{UlCRMegdnG23 zIn@FmaqSW()w7K(`?KIl+$=#%a*(6}?0_=TFi}YK01A$dm=3ts06s%4qSc2;=wvz> zu!xNm9XJ+hxC3~o5a4PWIhh0WqrStDgTZ}dj|voC8GAD(wRz%@0{Av1VB{+icpx$f z3=#ok#cZTPQV&s2P!JM`BvN7kLf)pNQ=5YGXhSqPEd%g_ODTDP&$)rm#%@8=K(|@s zVYn86ifqN2Ye_MyOx8VyNxttgXB<*&q!X8Oz+*@F;Oy1GdAJq)GJ#KQAzHznmflGmo zKp3!_lLO1hnE~5G7J-VhW6XXUS_Bk_x$bqRz|jw=uIoo8YlF23u&3i}YsXzDMNW}> z*HeN_`5b6A1Q>sCJ+>Mg>pnb(FiS`Qt*}TFBvC+pfG!{bFe8)51oCo#G$5TMB+lSB zuxgCa4|2iaU7PHQa|>NkXS}26WZIqqD3(?B_>*`?jZ@=F@YHx~kRm__6oGO0HE0ae z0rCKL>>REFXsBzz5y25tiN(ZxVimEGSVBxDiilQ16=5DfgiFRQW8xtL$W1IEI!BA?#Zic&xgA_;IG$78lIgKz>?i){hUl^T2x zehRO^FX4OfogiyaiZu{YLc6cXSra;3X~xjEr)1x~J_q39m3$&vJ`?Uqwo~t0+I}h3%1exD%u1EBe z)7#VPa4)Bq8Q#-cZ1xvxomQy(a}~PNSHGP8eDCEJPjkL6>(h}zpI+9fBYFG8p zFV-Z3=V>qQ0URn=@3dm(>2`2HnP3OpeW2Zf41m6B_cI_1$eh!``VeFV>lyqauml3n zaKKtN@QPEf>FKj9@Yo2b_Ew-Jv4C8=9VjELKv94L&p3bz009~X9H@?ZsxCOy0eD{Q zqYK<4fLES+&<%bu*n$TvVZG>&(k}zASO%}KgMKghxsVcU%>cFqK^pL?4WKnZfVY(j zsPY7$dl&{SnEZS|_B>T0kieb|__JR;r+N{MUC^^xuuT+H3hjaF{DnZl0t3}VufGIZ z0NXO2x^aP0f(6#t0)3eVEpGY~fvZ~t{*%BQ2cB>8_d+rLa;P1wJq_Al2CXq6hF^kN zM0-Gm(iX6fD*ph)_A4+OJ_c|c$AkK%Z4k{r4A}g;zD+36KM2ntENqo82?qRpG!5pW zEwB;khAVwm48zaxFJP)r2H+3^Lb-nxD)3i}Q}P zcmWkb&8P*_i5mQK7(lXvGyEn9IER5-Fvmy6s*!r29$WP%VVY1qCLg6>0L2{B4)ys~ z0H2MANkw;n=4IA5ijDIM@FwpDP_7IDr?bV|iEZ@`z@X}ZPlnTacVMZX1slO@6>yd} zfza!%#dHA{ahGS8h&YQ#1+Hvjr*{F)hLb(Zq)I1+H0kIgx7h^2ImdN)v40Ms;#S;A zgamIBoQ#wZh7b*=AD!?|LW@2c%=3?f8mhHu78XWkNh9tBj0iJ_0sLHl4J=3E@jae8 zY@3gb%YFHh~M$Dp*jbS25D2Gh4%>y6&8jj;D^Ogrxf$eWQv+zlf5gPJmBWZ+qM2YF} z*WzSeJx1&q#%y8>5h@DYFy3`?(a8vm;FZp7%AA=Rv1aIx;Fwet6=uYqNMG4CoY~eF zg$WM3DRLwcur(ZLZNSn|b8wq&m7M0C0q*p6WP{LgvW>5U5T6KF=c*)V9fbioE(R&r z-b2dqdQh0`IhbQK9n1!*FgwmMT!n8J)S)fI+T1864yYenu@-Lu;JcIH5uXd5g;;o= zlN97S+zeXutvHNi({WQ!;bD9b_gG6|oh)PN=$d~GwfZZ7{*#MawJwA^bY`~H6i;5m zB^@xL3bo_hA^VtnJ(O%%A9RWv8N7n zxNx3jyaq{vv}gvz2Q73H4DJP70<;71fCoLpTR>3Q^o-)e9zcOA@apKx#_iC)!~Vc> zz_lR3T=yb=#BXx(;2DhUD4xQ1toW1tQs-u<_969*_+XxtflWGSj@W$0j5hD~kYUUS zj^d3crl1L!Drgc~Bcdny4C#{^UXC%9G2&c>4A}Z3MrhJA!g=eRn8?m!1CoqiaaWLr zo_ho&orIC?DMu#pJvIRweM*h!wpQcHunFGMfSi2=m-?_o*t(s5uHBFuV8-_wxA=1P z&e>4|Lgby$NVUNCxV2BGaGYkMEFk4+HV59@BxjPFjRe8M&L+!xBE=`-+77x{B5g}d z{q|aP<$iDAihGdI=wOEAIxXlb$>yq}FKAgYV~?e*7Slqo!=duiu(=+BUqfj3)`6oJ zbF%F>IEE;~;{mu4x9C+O?ICOfjhX4G^(Rv7Ci}=k|b>kIc8Wu)Fl# zfr_AQQ$c9nzL_5Ht3TF}4GtBijJ)wQB~;^8p`#9ydyzHs#K9mK@*?L?j0g_U4-j6) zL&+Jz!ED5kfAP35WXNudls#+-%dv?NtM@TS%IQvY%8a&WPtUosd z2nqFW50z}ThI3qV&Rp}3BrSsS zypxiGb_3r=`H`5E;3nbA;4!Cz)?#lVRy&e|6wt7P1-=JT$;k-S4PmF;)f7-G`cTP` zn^~c#r4CELsqAH;t;RKm%#uP7!xi4Vz-?DArpA{}9`e)&R5`WSGI*JkVTA%KF(uY6 zdZA^I#K{ZiCNT*PaCEmvHDL2pG>wrUs zKpgvBdaFE~X&J+8A6C(a(G+7l;P96OCY^{qpe~~$AFdCWw6sUmJ1QMKQbu4Zaot*m z9fqhBinj^RvssB<;CsRZi1cOHwnKXGn!UiM^Kd~8j7nS_7DN@(;HhHCjz=+N-kP9h z@CmmJFSwUUh;J3Hb5SW)B+0Q3D(8)1#J&+^$X5bqV^sb$v<@)bUG{A92!0W2vX&DH z@MDOKQ0ngRaeQ5Hyt5l4M5c%|>pE6IZgtcLWZKg4Q^XlhA9>TNCZ)pT-U?97t(}l> z*J9U*iGbB#~a?2MTi_v05x~oaq+O-H-_mZG@>+*5S4@!*yKJl zj&V{Gl;ax(->Y;-d*HC8Ah3rxaX1$+Vz&mRpj&orh~AM-1oga+_@Mp@8_9%MFl+vd z6N;}FlNBg-3gF$71$ZMk!6GB@JQ^&}d^%;6Hm53J7?An45b%uMP?8q5nM653q8A7bwHt~)K%y12TaT* zSEi2*Pk<{R23im42rOKj&w?mPg|G=|g2(V#7!tzvcVRLxLyl6|j2Up7-8F~;mkf1- z8dEF;30(6fqJxMFkXEeTF}%s&LfCP)Vax;q+y!ymg@k-UF^oez&N$*0e%7Dn8^Dah zSwIimLa4)~fjUQ8pPD#`Z}pMAleoG7Gc@F_a!Uy|SOd5Fnq8@wO;F!y&I_S2z%P>n zs#geoxvoM_7BYvGqCBtO$@G+=U6^*b+*N_>B6;vCuGXgj*YZ7p42XiONuZ1-R6}I! zDsb9J@Lia6!T@0gyNIbE<`UL1Y(E$7B}(9Q;7tG&d5qStL)yG-AIr<~3jKUPo9$J64L#f$b>l>vGKlX9n$P#Ims8voCnE`y}bf)+2(3v1L(9zbLp{f4lik=^gg% zt@W)t<(my#UEBP}YtL|I%8|x73oit9G1zS7xs=Pi*Pyo=zS@+r_TI?1mJ3S1Z7wMN zEBm)<-;aOK1-$7oZ^XqWU09Fugy#i!6I5uX!*ZCvM|#$)lRRwNp7~-})vzhroLeis znX*P-Tf2@duYJZ}g#Q>b%buM6)$Um1Xy3^C2r{Ai)bmH-XA7&s`(jh~vCuCIGH_?b zrq|W)$le<)82nCuIp=$W|7iH0wW6`C`bdbE+xfJB=BDwj$M!~$2MFN zoZDqN0yP+q%dnsKsP}gIV&`x4pDc~#eBAtF*$;L9GWNsl_LtGpw8%OKerBAd-mSc= zxHoacWoTvTZ;re%^v+muOKtW))9S|lr?#f*pENb&6|$1yZ;hs^Uh9yy3*s0oc-dBF zau`#eaPBAEF5V&^7TOtl*f4}7gcA5Y?7WD?%kytY64h_kzBT&R?wd*(>yqZm&c#ah z9F7et{m_q#bo|Yp8=e))ACgDQeu*2c9439#-VOb@t54J8>9GCZq#u`jlru=0iBZjN zSE;CbI6R%-A?uSSO3i8fZ%q~03Ofs{zlprx_kKy{Mh5vEb*xrU8LecmFzn=Ne*vhU zA#=Aoa*l@`@y-lL6tGQhi=2{9U24BD{90Xd>esoM^u&(%)#R@DiOZ?y8Qk=6O<*m; zbQ_(s2W^j6zM!aFcMEUj|4BICHnldbo~`;FI+5`SGPLtc+{elNb-mmVZ9nFI==wEu zn{=n>GvTdq%Y2|GVmEw7@_b%j_9%7Bqw=WoHZ`lJ&!~TF{}0)Jq<-r7&F>m49etYXc{(Rwb=^q=fmG88;vME-YhAFs|8n1nQGi~jgWqCF4)x6vL?o>7@tNCq7 zoLHK2fe_7$90*Sfp;BUqW4IY&GNmg>70jcLvdDZ?SSuQr#9e8KOHPoyW_xY)b2O}6~eG+p<{t<6qj4HOs13>iA#chwWSoSdAe`!*xb_6_sb zyWUU9$^9z%t%(Fknk_)0G9uH%wIMy^T;euqj4Y#+&{{%UnVN`h&K$oW#&J<2W53=J zzaEz%cgZ-hBuS;9_6*F{htvkdz?FWZXX>bMFZsFbS&N?YID4mUr$&=<&wkf-J7x`C zUA=DpynCVX)46|_{Hk}@@KHw3$_L3mD(m7 z#INV&R=q#>jfw2^9R4@r-d#+kB{#li7G<18qvNBv;bU}qFsP#!R2ozrLJkvz^H`JY z!87w`Ny5sD@+)~#`{iuOnpk&1A~f+BkpC0(JsqCcMSd*yde8O1*w;)2!tY$2RFoW|# z+8BLd)6DAdWcFB;nX8Vbo|`|%5}MA-V;atl@+vq`#2}MLucOcdNcd5IinqnZcY>N^ zM-sreOta)#nl1eYsTS$}!gKy}+S3iK`9aOSaaGqkPl5Y<{SS-3tsIvOm;Ze7qof{N zPvNk9YUR_mKP)I0@0T0u9jUmY&>@~m&~dTs4b?m2@ABR?y*u>vu?$bD?kn4GFD53( z7KBlOL{)sEMlLN_iD$Wr8*S#u?L@<+F+W?#THt zJ_DvVQcm4~-MCV;%PBbNauz$QkEf1Gjx+7Dqb*CD&2lWVw(b}2O+2sDH)xFa>$e8( z3|^-$q4Oi3PEVwa^RR476!@=S9>DJ#5{sGjQxSwJuIHo{d!79#tbR#%>m){;t zQ>BikiNCg!Zb~sG3z7+M#>b8T3NtEbKNAmXA2@&qG@e!%k{GtkWQH$i*w{2S?G%RoXu@0X6>Zp<4N5|t}k zqtCjJD3Fkfu-)}xK^^jn-Nc` ze@*=)<>R!0w85USK~UGX?DOF@)q`P!%bt#DrYIxSy!dmL%W#6`?aZ$TlFL(;zXHD% z^TtFx`?U#K%7qe8>A=EOL?$q68GPnWsFdE%kcPLgd)Unpb5T{iB3^6sV02@&jyD0{Fc&?NT|4jzvexqlDu;N1UU}zIf8(oO4z>I~{G#QfKFJ zh1GbVFs(l$YlYi$YQY`q>cpSw7U}cQOy`7dY~_=J(cVuwrUw5b<2rnkcbBb8u`+yO zd~R?rhjdO6lX!U|zTj=e+nhJ4Z|ahGZ>GmXv9niJB^F^GZ$h^GrcZI3#- z5LAgTV`(GWBeXFMdos-Dws=&A+=*u{k>o;rQW`2gKVWo zRn~z+y*1A^ek`=-+KWy`Pc~1A?dA5_<31bfkiWn5JWCHfD!9-6!n6^yR-v#iE&Q?M z_paYbf15v*@!PE5CoR*~7B^S#Z|PDFc%CtMlUx$E$u$Y`F6b^1u6kZye7*Vg&e!|m z#c`uoMkSSEUQFECWiFkw7C~WhLYF|#wITT-{ULnDR9IP#k}m{zhHrJQ_9+B^!hOzNa(%w5=)L9GT)YQA+jVVMr zK}EO~hXPdmSUm1M-Z*yI25hP$*-@v}VyQbQH_w_fjBU@_^y`mjc1+vt_cBxy8_R1$ z%ITHy&x<}YFP43ts<5oK-kw$AwhOiFr{d=lOTJ?sB@>&1SYbjAi??}p;C#+S`0}bW zRhA;plN)7qQlX??Y`TC73(v6yGyGW|mpjARj3l!W);7z`s*d1BRItie9pSF9B*qB6 zgT5SE%;1FPhg4E)0wsWX!N3l{fbt2(^i|$fug5*V%(o z4jbz9BHe;kuru>uWGn3p!QIu{`8Q{jbJybkVFs6RFO(3JClv z-YmB$YJ!u=Y3HPHXpx=>NdywU2uRG`jEc}~;H9Tf`4nM*o=}b(1tZpwuf=0<6*>z} z8jqKba8|MUU4Xp%0A@D({RMYR{-ElS=&8`yd(dZRcvYA#QW3Q(jLAuh?iOZTY`)Bj zHOM(}j<{^F7uKt+tFXLJHYUxHz~Z$F-RB$5p=Ss9JG{m-IXzd6^yc6fjay@&|0KQq zdA>Q%hPrruHNKu=rmsazoG}QhMeyZand>SPKl?i64aOVVgv5lh*E8csQy$D~KHN7{$^50M9;klKTi``ufa zTlB3Jb?3d3dujJ(?{#ji-*0@-{V-pf^<;W?#;7%y9Hlzs-Zf|wzYsVYlEB=K#dDUuuEVQLuj^hKJ6 zx=j%UW(715^>_|$1D?R-fXvb)s75LA(mms@WGCA(Vb2HOvSZef!x4+{Kx!85b4{6h zSw_?I+UIt|up!rwY$!3r7-)tRL#m<4ux79t+Mkn*X-1o|eJ|d`+F#iJf9<{fTN~$@ zHar@QJ`h640)&udS(ariV~jB-M8Q}ggb+$7ODUz4vMdj!lv0*b%CeMYSzeaqr92!g zOIb=Nr7TM*OIS)MrIZjtDItU?lqiHK7-Lx$GL~f_gg!^3(R;~h({}d{cz=O8B%#r0 z?)$#xQ`dQ&=gE39U~WGW9UHA(wt0un#r2GP^AJ9@6FgXvz?Pu>-~?W6XaS!HuKXFel2lVIXedeo zx4PChMAFG@?*t(t(mktqB0l8KaBsL2kY7l4njBM(G)Jg|=`h!Ian&b1(BNJ$8n*gQtqr>c{ z-11b|3fZvcbeQ}qn1&cE!n6vXOvA&!#PrkN`(vYme)yww`1Cai=>xpkzg9MQwfkB0e!Xvh zi#Av?6kbEY4IKl;5T#`@ipB8v$!4e4j-Hfl+^^WA0Zv|4i@P-!nP1mkoZevGD50#{J)xgCBei0?`H> zoB-^;!IzGdJ?+^z^oorvg1_k$;q{u;?48OfF7(*MN#mj&ri&4@mhi{L=2=bw=WeQH3d>qQ6F9}+h zLrTGGz7|l}X7Ku*1^3)lXd{~{APGtvGE5q%(?lV>D~7!*36Vl1(jlT&ufNBKzBIJd z6HP0EB^G_%loVebM7ad~dXva$dXKY#mE>-w)?yvbB1Sg2z-zHecLJ>okrD*PCIzIN zYVtMV^YkfP%1{w8L;(ZEo4_sGVP5Ag7*zq;l!+78nbya7!OM;&M8%l0AELX=$VVbc zz9Bn+!zeV%x!JBAWC$hD#uGVr5s-^kh>DDqAfxUAYy;hPH=*mc7OoaAr3eWt*&R}I zZ|+jg!xh>{$jbX;>7w7U#WVQ63+3%R$PsQ>OQ|z>JYDvrSu}R23!1{K$yv^bWjY{} zTfQH8CErlSP;+o?_6_z2^XFU8=mX=qi6_gpwCm~f(`ltU1U@4j+N!u#anH;hI;@eG zzMp@?u;0btp6g!YU+FULhfF*AzP{(f-LCk`UBqTxBHCHp%YeS@GtU zDZW-UlgNLsMjCS*P3xo&8J8oZ#;TwsWX09(U|orMcPLhQu#Bahn>r{)1YuLUfskQ( zxw#wLvyO7cK`k$yvU)!~A@#knYmyU-zDA*28hR=J!;zqEY{nuJ=Nn9naYlt{DkRxl zMr)?;pOgkD9&6b<$QsS?B_c7P7DpPGIU0Dasqlv(2cLAymNHFzr$Gp=4bCyMVE7`)#@z&Z7L!%E0W!d*f z8JWyM=So=oL3QAIaOPuGg!Lem8&8?EHH9TTV1=jQv5qK~+ELBf_iR2EidqguAqIQ$ zaaDMiQO#j{_%>A7`M8d;Oi9Ey!i;7jpa}B(ootC?504AaK3QUMoPD%vw$Qx|$Z<24 z1uoPD_z1G;>7z|~N&v{*ciD(l-w>`37CW;M%$rS_4oGy)at7=%j2y4UX$Y(FRf1mA zs3SC}fjMkS=H$B;eMOAPD5dz+^4We4Ru`GB0u(OIg>W5DXLIQptf>e6sj^cjNX zE%X&|gx+RU;j5#!GfbW=s5D5WRia$NGqk49CtptOdTToNSqQ&nV?80MPxskN6n+UKyAGS+omU>4NzH9 zLSJFv6h0D1?WZoIo1jdSgr*>c;Qc!ZwFSAL;TGePf@erGK5lPuB)Heyn6uQDZ{yg@ zT=C8fSFe)~zlozVH_)x-kQ_0J{8jzn#W(?V0ZIjl;$oIVEkmqM(WI!9$wrzuJwXfd5M97R5wyqEn^-#h56 zVJN@tGi1U4JlfjxGtRFk-(A}6akldfSDVuY3Sz%a{QYc0=?|vNuCUbrBh>)+M?Fx9R!N;WSSh6>O*A_isCQ!|_M99Qa(HJ33K$M;Wb|5*C{-EwxmIJGJ^@&dx0XGj^LfW(iS?6^!m2Q7+~ z#VZqILB%0`l!6W>7@ywNtc=XnI;?^@UF-0Y>Cp`@tRnb^dyu$d2J?p`p=yO{&DD=SvhOTxmVM}R;lTZ zX49-*sU3M}cFtkDL9-E4vW)AI&#{$FFREW|e35fYAH5cl!)kB~4&>X&%F1Nr|A-#k zK=r9tN)H8rmZ+UqI=?OYL+t!pD(hR)zcSydxa7YAneDSCiGhD#`*7PigoXy?ip9}U z&**M#D63vt%~*LU=5|cHM%EmX;j=yL`?&0#l9~0NZTy8RgMWm`bo(7CYLX5ro4T1b3GO*O)x>x#s`frlPhW}UmI=j@rOR@;Uumstckn-^#{Hd+{s=OSLV&9&9XbpiYNM`3Y*o{=Mymb zXZp?+M2{$%Qj%ZU%u0S`FNOYWQIz3qa^Qex?6FSM@Q(5g$xo~PR|Ivh?SHi5rAxb# zv%ijhSKIK%aV7jBL8MEeJv)FUyW&8)yEy? z8Vk!lKvZHq0h$o@rK%f>=lH4m%+{>Ymvy&FZ**OX44tE{+e!||9rWFv8Q0H<&Of&D z*=z3qWB%{Wk2?wDxz^_zzr0o!Q!9o&-9IJ&L+!WMzDz0#C80r9a?){V%XcLZYhDyJ z(5qNz==z1WDEwyK^Ol#emr|AU@mWzlXJZ4c-Xu%ygW;XZwb)=b&SOs%Z_SNZl z=IxE-U8PK6yIyf=FkBTtdT~qXgXZnH)ySEgpH+VDFYwveO8(!(ndDDVFvd4*FHz^WZ-my2=TkwH)LH}Pf{fa-s_5Cewc5nYie9w%OUx<8uHh1S6SvB>4 zh$XK2S2gP=Y!=^J~T5p07)TG3pxo z{%+^n&0p($Y3?TLQl(&@ol29V!;mrR_0@q>P&H>FY~t+3MeViBoBiMs>bkjcef4sJ zxRu|IeBvT}=gz z^R$zRQVdHIOZSr@BApm9vK=Hho?_|t@1mX&yV%0`th=q zzv@gtF2;oI1a4!wWE8&Q(mQ9JHg_|nAy{@UG_vZt>A6Pbcv6?5H(nkK4vS(#h>_V$ z6g}DAn|iUTlpDz5bvkUg0%WHKH)38!;m%^6W{5Rsy-BAYqQT(_2d513;W3y!A z5t(PC0kxD(4+;)p68j=C9nj6UotMb-Z|pv!e8v*Decg2pjhd5cBL=uT)Gqt7p8N6M zySg{m{yTkG)cvRJ>HjbDU&!(8_i_(0YMgK^ZYW(^SWx}Pvs&^$G*z&^s!W}D#u7Co z(S}cQda)dDrCZ`?cVze$*#bc_=$2crX|Hpx8?VhrPh4%17fBO9{l1QdI|PU9J40I; z@5KD3_4UD@^#9qNkiXrSe_MZ}baUeXbQ8}B5>%Oq-%9?x8in)sk>5hUx{#iKYx{;S zk`uAcPN$aO633X`;+~?$1lNKKz3Ke8EH^UcGA~MWt?}BFeB(l2#2#lJ6*%KeEZy`c zdmA~6wXYAqs&D&RE z&a#8{DBJE$8)l1i7b8)OGWHhF7EydY6R?H0tHKz;wU{fjGPT&qr*pD1N^`FIB`H$IuXDF8xHRZIY zDiU0gd_e(cj5$tChwRiaO43a9Vn!D`BP2nf5e-TjFVLs)|k@N;;+qC}V*PD+KT8DyEUH!g^@J07p;{Z$GRzBKn+3q!k(j z;_z5-Ro&&J2ehEY?u?_AgZ4YJt$`Knm)!p>>Y4hB)}uNlKW%-ze@UUKbIx#6WQ9qM zxlw<^{N~zsDr$6fmcLVdx2+=c8{)4Fyp*fhh^e@=Cmav12@(f$SUa?N21%b_Rt4>H z5_xF2G+fSChP%Q?0DE$ssR*uT_t18{r4E6)TR)^JQz_pro*MZn{a;i5tbOcX%DeZ5 zB-6{Q=)F|;Bu9IJcqTtBF(>h_v%VGiw|Ui)>fB0mSI+^_+y8|gd*8O|(zngjs`)Xh157B?#KfAoz_c4A@ zVQZ(zxjW+ZsMTi&Q;WXXpF8@s?Sk$?TOpFa|CNldL}%5eCB2|l%-^VtR$m+x&4*`l z=>e;l7&40WM3QF=kHeSUN$xne#4}B3K{eV$Xx&-P;uFZq7>y57?$&%<@ImCeuHW`A z%`a9hHow`kAbYc5k-1#5TCmZeUjKAoOMg^*EQGUV0c(Oca5hUe5M{d_^Nd*$nY3|x zR=N0m)AIwj^@;Rnt8Z4u806-Qt>SzEixW)N5jCG`WXvGm-;RIU?@lnb}Y@yigz&mI>>G$!C`%)Ph*3>Imh>hE#ABXY_0& zXfrUKDP$!uvgtFlc5HxJN!>#SAn%?IRrj!)LonU@?ta&f zz0_4;ci3hfq;=GhZXYoU(PF7}7)~9c=Fo6z7Oer>#?s)E46GV;U`$L# z&4h{rIp`CYQcR#*YoLrGouFKs;@kDD`BZ>1NXZJa&0FNH^2)t6WEWI>rhA8B+h%(+ z$$W2$H=V@6do`ad10*7gTqpAY1yK09pX%|pl1WGg=-e*&rpQ&$v?YPb#Dv_w2=r`Y zk!p$w*@U=6L)+^J3-FG5${sRGaUqSAW%#TOJ`sTT=`zBDM?EqRD!28Jlc&S?7k%-N z9rm-wmH{16f@W?bAQ38GEpV0`z7fDAmH~;V1Kc4CGVf!+5$W=!1B%iGin}ZD`2c(} z0?X{eXPb~G-v-7S9rSIP5Z5@+>je))KuhYt^Rx^!cKx_XCaCU~L%-|c6F)wY3BSKH zVMzrN1rDlyeOwMq{y!_^vUQLjS3v~*y1P^G@FO68{oQ_;`ryA|nCqYBq4z7k4OmtM z+%p|$_Uhr;{M1R+0h~C$dhKVAKPuVc&`&=H&_5rCvHDxn!#H-Ie&4V9>;KQM`|F>8 zzXQGaj0ZN~k}nHs z0(KpSOaPXlg0(teofE!NWCdC(1dd$|e6|SB+t7Xm%(ViOj}GhJ0l&>rcx*ubQjj=U z^PsO1-q*r5+<^7^N4WA-J$Vu0OhB|*z!_WuY-JEe=YNj@p0)B%g3YTWG66ZBt9mf)LDSHv;#{JuuR`J;ag z*Ov#M7DB)L`%tZ~6Jo&hZ2~F;gE65&7Iz&M4Ga!jD3e+p2N=cM5MInJLSnu6Om%SLeM+%S@vY%ps z7zj|7HxEtlE~44wAti=F;D93F?LHA>-Ze}IKUZ8un}uv~1@QJ(JT_W3IgKc(q-UM7 z3S(@b^i!<9To~Q9w;!ni#3&P@kVPrF`=frk55h%_4RNhfWz6&Q(xGE1m@h3EodLfF{2 zryiAn7wa}05p+){R^pT48+5&+of?hQ;3yrkyyysFBpVSkp{K=py8zqmM0ntkQVK`N znpaP-5ChRbY4S7JP_vImDJ7#JhC1YsTIQRlZW6V?_G}|ls1x2{G?~H%jIABBdD5X) zCWVktdc1r}E@cYkkt}Qg8zD=PSh5c-XN)4vv_`^$sAyJS1GOIV@q3;kstZtKy*J-2 z^R>f~NB4+mGGJnEQDevy3Wj!3E4(JqOiyD}a?}B>>`;0%caRl{sHri&PLB;4B9h&q z-jS0;+oA*Rd2RWQPXi%@;)&yU+*)kkHg7mbZBh1ayUsoA8g}8n zJWm!{N9&-iQ*&7zRK0JBmW6FW-Gr5BBW8(IHyf{XiSQ{`g?q%MaqGOBo;h&cZujnx z1XK(dkzHz+FP6}uEJTa!P^F-rJ`Aoi14te4KnzGS8jXs`IEsn}crjXyVPr9+y5aXQ zGR7(ZPGTX}iwuD$$r!c8D~5AcwYQsOgU1C2O<<0CnmmQ{Xrvakp{YJTwF&nATu^}@ z!e&5AzJ|f%#xfjCl9q|~)2!GGcy<&~I_y;*pij7^?ozVPSL}%b*LsIH8PikyKs#PW zk;757iFA?GxSGoHvWYF<0%`FixF&GSU4oZ*r(A{Zb-?}UzEvWTZ1I+pX_PEqIoSbc zg8{FKT8<=oHNcP16760T_N&hX)6*!u)CxL|s%hKII$9(Z9Dyl3ngdJnjeCbY_2h=P z#3vh1IL35Pe0 z93d^k&m>p=18D{>kRE)NnScU! z!nLChoZ2_w-ek~g1ox6tke!_2b_oZfOn&IR=5?uYP1zUq-f!a z;_!8&8I%IDgVc~l=vNM~%i)@U)lk#@KJ3Vrw~AaK3`83+$E$q;8k4DI^kG>P5x6;Q zVp14+HmN2$+*$4|aEv+hvVErpQM}aYJbrs=JWGYfktwl4aD4YwV7>Bw=?FCj5hNNP#v_z~5?T5ZCdV7gs z&k|AVt#ieBEa0^w!}1Whca`XZsT`z49jq3 zS^}?-9&8<0RhiJsO@yG7(HH3paPFz~mIH&m4Q+($#UbpUE1pOsindNeuvY3U9Jvyr z)}3kJhx16ccLSZJ#|LyWak>NZA5}bvAqL(OdOXuD^!m@SYM92s*&v@{@uYdXD0*;y z5PRC(^<)*gj};)v-gI<dGp-k&PhB5?mdRk9(pmRro}@2nH*D5QOdGgY8%FtP+4Om5ll*GrpP_jZUtfTY!M{E_C^uC z?mF-rX@s*&6X3p`j(YGj+VYZ=W!g3^3miw%Xe-o2EDeqK9y(J`oo8khG zrp4f4b`&F)f_voGX_YGU1{5JCm}Of3L~D{MmGDFsd}0P-NzBfyd%fmLl#imf?Ccb z1k@nQL@g0dl;Q%s4&Njfz+>Eo@+r7)6VB2~yo<1STtq)?Jq+S>lo$Ia5J(QTpJ1F$GD& z`<)5SPIsKIow9)qVRL9EBJ;XPF1mq8$Qh&3kq}LuB4U$l zr%WOP9-F=G#N_DoG@`l8gup?Th#|&i;J9#La!M{Zlc>QBJjTA}PDHiTbyDexay2_M zahVsmjleWT&<^lA%EH>ve99QP=8AKPh)E=x9!0ac7tHOCV;?s>VLL*}VYHo@#~u&1 zLPhfgdx_HNG5@k9swR$K9<5DhltasNq+hfW`3Y=WQt67w)v6PzAUigeqiO^N0% z#}1{384FdmndmgJ;pXGzpdXPA*R*|4HxWY$&>coofD~&Zrd*hl<(>m>VR z&jHfE3=TsGrbJYP!87F>p_>@#7@YS!Drc-S!_z{_0c)2MS)e?zz}O5Z4HO0%=_p0) zu{tCUlildjdQ#jSPMb^S5%_8;T=I!iqLB;o7vEx=Vyg4a8%z;S2sc)((?ZQwmV6kSJTq#EyZ zxm;Zy1*Mdxr?QbMQsx^2W@HPimNg%+%@VVs0(8s{<_NPSK*~a)Vzvu>cuJYPz>L67 zRyV6EkQq1_5FZc+E^ap9MXk822^Kl&9dkE2vg~8l3~QZj-Eruga5g%GcB7SRnSC;9 zDt}@;DzJ6JS$M+3cCR?+T#NWV9QP~WX=XU7$5WVsu%z$_?l9vJHPcOO8MpLI2Dgtx z1a$;kd8V+0a7)M>yNwwiKm@i0^#;{(5`z;t9f2dvIeG%M+`HqB`@}$A$I^KM;dN{5x*Tv`xbamRcdpY~7cNY%|EFF#lJP$8)Zvexc!9sxXJ2$PW#NN}Mn>j2m76T$h>)y%)02$DJJu9SL4zMbq+p z*>0VkbrO11VNyLxdnhpo4>tAmgCTv1PI=D?j0ODn<*IoVwx#)f;s-IClI^Db0rM)Q zIanl+ilT+J;kvM0USV(oyE*`6O$RrGmx*g+eV4~C7hjIPoOqcRnRU^4VMbCYS&@{V zH;5JIqs2RCO9iHo8Fn#s!d+s?Fs>WAAC%rF?=9XFX>+v`TIu~>$nRJ`*?GTWWoj{L z-u0`+dHLJY532M%-k4CwrIMKbs2*|7nF<=;Q)=ryc39f&77C6O>Z@9EFX-~lO&p$&R$VP(W4ZXQzO+eUC-3r zthlL(<6axP%#~>*TFICsRnQ%zqpu^gM1rT#4GINNottAHu*F%6jAQq*wwu?LOM7#~ zOwaV#FFM{x|5eG->W8W$H7_bA?YXR*d(vs%6t>{Z1P|4H-IO-^pzgR6RfUX8G?6*- zGWpzP$EDRPn{lPjauY)pOEL1Oc6q97399K^1u0xsKqX}e?({@($2R6*+3M{q=c0pi zl5tqNo2gR2l{womojRqSh@NPlR=+u?((4t>+*mBFKe;J7o!>WUFZPskneDY@b5`m^b`3l+>be$?=#-OID`8ktc%88*RWLLFueF-H{O zk$~5&+8bQWc&(eXX6dWdBfoY0vTf2ZvHf2gKgTAzW~P4|xmSUfUanT|zgY7uxYGvN z$o%7>huHn)Pgga_ef^P^iV7Mdtt10a<}~ zn_m|k8khz3IdPs%@QhqAD^3QS26wfKf4r-&`ec0FGH;pWj3NJN7)^P7cBcQW5{-mX zE00MvWRxYTFLiLU$@$}rM7}IbNEtink=jRg<*LqH^CQe?J=0{Mrm5NMVje@1zgIDmBq(cZ=~Ls zzkxkFt&D!5{#MD2gvezH?_$kmQ`F=YmNZUK${i2P3|OPrA{uZO)tIu6+U#9;r)S*0 z_-J!izYZ+0srvE3kzo9%dE^ve9fxqCFjT_uIXa9TcKNUae`}gra*RXI_ z`B5!`Mn|TqUN$8)L=6fh++nonSbJaaQTck4YV7W~wVkk)nlipYQE{M6Hx4%|8F!Li`Pr{FlKkIlMUlAT2hH4T8>6#bHp2?5g3u~c|x`!Uk zd@`dN+pv5zeK6xjgH}W_SJH13CMKpEv!!1u%glOlA))r_Ol0$=6q#Dm98t@w2Mg ztU<}^yQ{5__rh16Nlr>mD2dsKcCgTMdnv9r66udl5n;yLUJaZGeuuH5gzMZ4X!rG z+38)o;Kp2RQ5=sxwrYyr#;3AgTltCX)$X7643GXt{V!YYtYM?)9nrHlS8lY(B^S6t z5w{${jLn*%kEVAr?iLz%aWk!x+bxz~>%OHkrSn(TW=YOD^9 zyHtHO?ke}P@#69Yg%IP!l8r{oZptT`J8SwLOOc03>kr-#rN(Bat>((U-u!jTmp9Vc zFKBK>CzM>HpGVL1F;l2qN;SHIruk5O%CYUpa@XL5({U_5?7h?e!NzjWuf0%Xg34q59`I^O$RGGwC(j z)x-tMFE)NYIo9*q+*t4H8^1f=X}9g7JoZX;mUb5phtAu`pPGK zn-w=x(>RM~lcNWpi_I6iX<)z~D^w2!4%va`GuozF0Y+UQS zjce<%8cxL41!IO*{bBZRtL7|I>haK@hyJ{C-0;T6yZFJF4-};$3PjcCde7~h+Y;9E zwz0{Rg@;M{)q}-H6&9Nlv>_PPf~n{t<$R_s$C_P~6`dh@Y30Sz+pZXql+JHuEzr^+ zKNk;q0nUlgx_ctEciF|(L1W4Nl#iR&XO}owwZA_6dgvSTZ`FUKHV>iQfkh!X z5h=oUk>y;jz{ZJ0vro1j=RUR>XP$rytFw~mU?d0@u9CMB($}*pvl=qXGS*+7eQ8Xo zh~-_}3m*-vqqS1wd~(mGeH5HdYmSGl=_lxM#-oLO^GDemy}wI)t7eWp)AozmDcP*{ zcd2)H$2_E!wZnDsqQa1f

#nCa4CjdPe?-6YN|640ZV&kd#azA}@6 zr_X*t_=TLb5+!+~O~Umn0DxDa!h5SCZi*}@fATvVOsrdi$ZoYOP(zr4vzM* z37fn3xZ8qQ)*f{lI0qSe%fX?R_wmMNs>-@bEDx`2{4VjGocG8NOYTLP(oa+_9z{!Q zqh&Lu;e2YN7Lx)`7oG!k<@+v&w*}#VhK?1ZAVJm@)gN2=tT>UK)b_mY z`O({Rio)ybmusamVJuG&)JGp9*Ig5EZ%!P=0q4S^)80MY+53?5``Y*O-qpOrTtnV1 z+8F3>gqsOc9UOWY|%sj+M>*6pY_M|$wEN1rR zOv3bYv9}9v>k`|aDZXZk?7FZXksGoYWCmQR-L-epe(W-v9u*$!Ygcz`J|0ykZl!#n zct2;u`kquJ`q1>rl(ykf%8~6v09mJUghx%H$!Ig^giq>i^GH3Ro_dd(2=!s8ni0vZ zi0Ha7AGvuo{YFQ8?z5~!?Q?0*rpM)7bzI_K*o){0u>>{I3w>&LmA&3-HftWmLaRl0 zrgoaP_tmg?`+6XG7QisMp+M-FIm5ZNMioQ2}Iv#z+HEM~o4zL@GE| z*HJKT^x{aN6*GC-qgmM;;Z5^uT@=fTq=@`3AaNf9FjptONm1lVwWAU zjlBn3+PuAH&F07WN9qryTTNTh>e}tSPqOZ=K43kneUfh-bC}$8Z!{>_rBKR|b;ycC z2Ho501*8z!Kr`r_fulTgI9t?kK^_?sRUOk6+kV4vV=E^9YTlLPONZxE&&Gv`gLMHr z)EzR_vt!>n5QZ;AdD~Mvmfcuw``6f#pEW4n26Bzju}==13Q0TbIZy6VW@-+t)Ip zqGb!xq4N#G1VLAbj6F_AkP&x*(_)P>6&$J_MDF+8nff$IGpL#Qq>!IyIgSPc<(VZRm1vjbb{xt56 z{x17I`5^BhZseM~kE`v)E|Ev$%?BKM3RO`Nr~}$$Z~~$O$Jw=0vwJ>WH?p z&FALDZRd+63Q5NKIx+LyhOkyJ9;W1Vaxz)7voKb9%mnISZC~wkiRMbtbyD!v5U@1u1? zmOfv{xi8R8+@oukwW|9y4~h>u9`cM!roN*-YpI>-f?p_Joo^Byr=>CbSo7?1PC7S> z=L(q(Q-o{669xN%=!k&`OGH(~1n2~eg&9NgpuN!`dZ3xHjP+AeNU?|M8nh)J^US+O z>~QHo`=C!}zCUq)`hLEy;{mSUF~mNac${dqSq4uE9m#GR;Fvv>dMu5x9^eR!@4!gPFXc#&X5kQk8~p@;~LfcmHK(y+b|^O;EQMo?j(gUO~lPz(`!Q*fn|Wn&-b zn42D(4@D1$4$}1H4@^4FgVhI1dYK{Zu*;}2RUav>k@hH;3oxHiL_#g4Z8G>QCfg9y z8O%R3z#9pv4J{3$^Aq@4d^vwU3=7*2spqlJpx`>rAEQDGT$Y#h<c$U2bh=^K1n})pcRA6V2 zFu0Dpb!MB_6p|de8H$JU!Ze}jq5UC!ydoGGROhjgK!UkQuL2Y3PM^%XgeSQ)_Mwyd z+iYN=dA`# zJ(r#YIeR^Ah?Y(}#Og7UT2DO$FZM;q{MSOANE%dwNufoNcZ$dWRA>?$=6auig3%_25%tuLeW0{3r+Vud;hJ}QC-6SYzHpzdx6D%LWA zEqDk^wgQV?3X1D%P+L|7W0d>YJ}bEo2>KE^O-{kXUze~BPW1+oAo);-mIIYb?SSxZ zfU-FqYI2gHvaBB1_9IX|)>@4-mO9#qew5bQw`N;DNteM*HyPdz0?-e z1kD0>dkB#5Zm4`~19$mWcy5RPyJ6lCeCB7LufdZ0(3YRmPKR1GA@ny2eCgvr3ta(7 zYtqw60q^3WIxiX+_)@6e5 10*1000) { + failCount = 0; + startTime = System.currentTimeMillis(); + } + failCount++; + float similar; + if (compareResult != null) { + similar = compareResult.getSimilar(); + } else { + similar = 0f; + } +// new Handler(Looper.getMainLooper()).post(()-> { +// try { +// Toast.makeText(App.getContext(),"识别失败"+failCount+"次,similar="+similar, Toast.LENGTH_SHORT).show(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// }); + Log.d(TAG, "collectFace,searchFace查询失败"+failCount+"次,similar="+similar); + if (failCount >= 2) { + recognizeCallback.onRecognized(null, LivenessInfo.UNKNOWN, false); + return; + } retryRecognizeDelayed(trackId); return; } compareResult.setTrackId(trackId); boolean pass = compareResult.getSimilar() > recognizeConfiguration.getSimilarThreshold(); + compareResult.setSimilarPass(pass); + Log.d(TAG, "collectFace,searchFace: pass="+pass+",similar="+compareResult.getSimilar()+",threshold="+recognizeConfiguration.getSimilarThreshold()); recognizeCallback.onRecognized(compareResult, getRecognizeInfo(recognizeInfoMap, trackId).getLiveness(), pass); if (pass) { setName(trackId, "识别通过"); noticeCurrentStatus("识别通过"); changeRecognizeStatus(trackId, RequestFeatureStatus.SUCCEED); } else { - noticeCurrentStatus("未通过:NOT_REGISTERED"); + noticeCurrentStatus("未通过:NOT_REGISTERED,"+compareResult.getSimilar()); retryRecognizeDelayed(trackId); } } 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 2d33f8b..110cf21 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 @@ -10,6 +10,12 @@ public class CompareResult { private int compareCode; private long cost; + private boolean similarPass; + + public void setSimilarPass(boolean similarPass) { + this.similarPass = similarPass; + } + public CompareResult(FaceEntity faceEntity, float similar) { this.faceEntity = faceEntity; this.similar = similar; diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java index 27c8119..e0e4387 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/facedb/entity/FaceEntity.java @@ -45,10 +45,15 @@ public class FaceEntity implements Parcelable { @ColumnInfo(name = "register_time") private long registerTime; /** - * 用户类型:1-普通会员、2-临时用户、3-内部员工、或者其它待定类型 + * 用户类型:1-普通会员、2-临时用户、或者其它待定类型 */ @ColumnInfo(name = "user_type") private String userType; + /** + * 会员编号 + */ + @ColumnInfo(name = "cardNo") + private String cardNo; @Ignore private int trackId;//人脸追踪ID @@ -66,6 +71,8 @@ public class FaceEntity implements Parcelable { this.imagePath = faceEntity.imagePath; this.featureData = faceEntity.featureData; this.registerTime = faceEntity.registerTime; + this.userType = faceEntity.getUserType(); + this.cardNo = faceEntity.getCardNo(); } @@ -75,6 +82,8 @@ public class FaceEntity implements Parcelable { userName = in.readString(); imagePath = in.readString(); featureData = in.createByteArray(); + userType = in.readString(); + cardNo = in.readString(); } public static final Creator CREATOR = new Creator() { @@ -145,6 +154,16 @@ public class FaceEntity implements Parcelable { this.userType = userType; } + public String getCardNo() { + return cardNo; + } + + public void setCardNo(String cardNo) { + this.cardNo = cardNo; + } + + + @Override public int describeContents() { return 0; @@ -158,10 +177,9 @@ public class FaceEntity implements Parcelable { dest.writeString(imagePath); dest.writeByteArray(featureData); dest.writeString(userType); + dest.writeString(cardNo); } - - @Override public boolean equals(Object o) { if (this == o) { @@ -176,12 +194,13 @@ public class FaceEntity implements Parcelable { TextUtils.equals(this.userName, that.userName) && TextUtils.equals(this.imagePath, that.imagePath) && Arrays.equals(featureData, that.featureData) && - TextUtils.equals(this.userType, that.userType); + TextUtils.equals(this.userType, that.userType) && + TextUtils.equals(this.cardNo, that.cardNo); } @Override public int hashCode() { - int result = Objects.hash(faceId, registerTime, userName, imagePath, userType); + int result = Objects.hash(faceId, registerTime, userName, imagePath, userType, cardNo); result = 31 * result + Arrays.hashCode(featureData); return result; } diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java index 7fe5798..10200c9 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/viewmodel/RecognizeViewModel.java @@ -283,8 +283,8 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback { ); } // 填入在设置界面设置好的配置信息 - boolean enableLive = !ConfigUtil.getLivenessDetectType(context).equals(context.getString(R.string.value_liveness_type_disable)); -// enableLive = false; +// boolean enableLive = !ConfigUtil.getLivenessDetectType(context).equals(context.getString(R.string.value_liveness_type_disable)); + boolean enableLive = false; boolean enableFaceQualityDetect = ConfigUtil.isEnableImageQualityDetect(context); boolean enableFaceMoveLimit = ConfigUtil.isEnableFaceMoveLimit(context); boolean enableFaceSizeLimit = ConfigUtil.isEnableFaceSizeLimit(context); @@ -472,13 +472,20 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback { } } + private String getUserId(CompareResult result) { + if (result != null && result.getFaceEntity() != null) { + return result.getFaceEntity().getUserName(); + } + return null; + } + @Override public void onRecognized(CompareResult compareResult, Integer live, boolean similarPass) { - Disposable disposable = Observable.just(true).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> { + // TODO: 2026/1/21 测试使用 Observable.just(similarPass)代替Observable.just(true)--- + Observable.just(similarPass).observeOn(AndroidSchedulers.mainThread()).subscribe(aBoolean -> { + Log.d(TAG, "collectFace,onRecognized: similarPass=" + similarPass + ",live=" + live + ",userId=" + getUserId(compareResult)); if (similarPass) { - if (recognizeUserId != null) { - recognizeUserId.postValue(compareResult); - } + recognizeUserId.postValue(compareResult); boolean isAdded = false; List compareResults = compareResultList.getValue(); if (compareResults != null && !compareResults.isEmpty()) { @@ -500,6 +507,8 @@ public class RecognizeViewModel extends ViewModel implements RecognizeCallback { getFaceItemEventMutableLiveData().postValue(new FaceItemEvent(compareResults.size() - 1, EventType.INSERTED)); } } + } else { + recognizeUserId.postValue(compareResult); } }); }