Author SHA1 Message Date
mazengfei 86c3c07e42 隐藏测试代码 2026-03-12 11:10:26 +08:00
lvmengandClaude Sonnet 4.6 aa96455f66 refactor(face): 优化人脸特征存储与增量同步逻辑
- FaceDao 新增 queryAllByUserName 查询用户全部特征记录
- 改为查询所有记录后做去重判断,支持同一用户保存多条特征数据
- 将 Thread 替换为 lifecycleScope.launch(Dispatchers.IO) 协程
- 提取 getNewFaceEntity 方法消除重复代码
- 新增特征比对调试日志

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 16:02:13 +08:00
lvmengandClaude Sonnet 4.6 786f2a123c feat(face): 人脸库为空时自动进入新用户采集模式
- 新增 newUserCollect() 方法,人脸数据库记录为零时自动显示采集界面
- onResume 中异步查询人脸数量,据此决定进入采集模式或正常识别模式
- 修复 isFirstOpen 依赖 1000ms 延迟的竞态问题,改为立即置 false
- 顺带修改 FunActivity 按钮文案为「清除并拉取全量数据」

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:01:39 +08:00
lvmeng f2a6de55b9 优化默认值 2026-03-10 10:47:24 +08:00
lvmeng e51864d400 优化默认值 2026-03-10 10:45:16 +08:00
lvmeng e63d3f7624 修改会员字段名称 2026-03-10 08:32:18 +08:00
lvmengandClaude Sonnet 4.6 0a110020b9 refactor(face): 重构倒计时工具,统一使用 CountDownUtil 实例化方案
- CountDownUtil 由 object 单例改为 class,各调用方持有独立实例,互不干扰
- 修复 onCompletion 取消时误触发 onFinish 的 bug
- LoginByFaceActivity 新增 recognizeCountDownUtil / leftCountDownUtil 两个实例
- 移除 CountDownManager 及其所有调用,消除两套倒计时并存的混乱

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 15:50:36 +08:00
lvmeng f99538890c 代码优化 2026-03-06 15:30:25 +08:00
lvmeng 6d8ca7c83f 人脸增量接口改为间隔10秒执行 2026-03-05 16:21:45 +08:00
mazengfei c6bcdc03ba 增量时间戳为0不再限制 2026-03-05 12:38:48 +08:00
lvmeng 3b88b011a3 全量人脸数据改为首次打开app获取 2026-03-05 09:43:41 +08:00
mazengfei b96f9872cb 优化 2026-03-04 14:56:16 +08:00
lvmengandClaude Sonnet 4.6 eef13ff56e refactor(activity): 将 refreshFaceList 移至 insert 判断块外,确保删除操作后也触发刷新
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 09:15:00 +08:00
lvmengandClaude Sonnet 4.6 6c937c5f5f fix(activity): 修复 queryUserCount 在主线程执行 Room 查询导致崩溃的问题
- DB 查询改用 withContext(Dispatchers.IO) 切换至 IO 线程
- clearFace/clearFaceAndGetAll 操作完成后自动刷新用户计数
- 删除未使用的 CoroutineDispatcher import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 09:07:48 +08:00
lvmeng 513f4dbaea 增加查询用户数量 2026-03-04 09:03:32 +08:00
lvmengandClaude Sonnet 4.6 037816c303 fix(activity): 修复协程冗余 start() 调用并收敛方法可见性
- 移除 lifecycleScope.launch 后的 .start(),launch 已自动启动协程
- createBlackNV21Frame 和 emptyFrame 加 private 修饰符,收敛对外可见性

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 10:20:37 +08:00
lvmengandClaude Sonnet 4.6 dca9496bee refactor(activity): 全面优化 LoginByFaceActivity 代码质量
- 移除静态 instance 引用,消除潜在内存泄漏风险
- Observer {} 显式包装统一改为 Kotlin lambda 写法
- 匿名 Debouncer 改为成员变量 openDebouncer(修复防抖失效 bug)
- Thread.start() 替换为 lifecycleScope.launch(Dispatchers.IO)
- Arrays.fill() 替换为 Kotlin ByteArray.fill(),移除 Java import
- 清理死字段:regDebouncer、faceUseSwitch、faceFailThreshold、tempUserId
- Java getter 语法统一改为 Kotlin 属性访问
- 移除 protected override、无效 handleLoginSuccess override 等冗余代码
- 顶部添加 @file:Suppress("DEPRECATION") 统一处理废弃 API 警告

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 10:18:39 +08:00
lvmengandClaude Sonnet 4.6 08f1963054 refactor(network): 优化 ApiClient 代码,清理死代码并统一懒加载初始化
- 移除未使用的 DEVICE_BASE_URL 中间变量,直接引用 GlobalData.appBaseUrl
- okHttpClient 改为 private,收敛可见性
- retrofit 改为 by lazy,与 apiService 保持一致
- 简化日志拦截器 lambda 写法
- 清除所有注释掉的废弃代码

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 09:19:49 +08:00
mazengfei 4ea95092f5 优化 2026-02-28 16:54:57 +08:00
lvmeng cc88dccaa6 识别成功未提示及采集时间长问题优化 2026-02-28 10:03:45 +08:00
mazengfei e979322697 优化 2026-02-05 18:16:38 +08:00
lvmeng 1e6ea1517c 人脸识别+采集分开处理 2026-02-05 15:05:45 +08:00
lvmeng 2ee4d2d320 人脸识别+采集分开处理 2026-02-05 13:32:45 +08:00
21 changed files with 1822 additions and 400 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-22T07:28:00.026342Z">
<DropdownSelection timestamp="2026-02-05T08:42:12.462597100Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.128:5555;connection=c168ec92" />
<DeviceId pluginId="Default" identifier="serial=192.168.3.114:5555;connection=a41f05a9" />
</handle>
</Target>
</DropdownSelection>
+1
View File
@@ -69,6 +69,7 @@
android:name=".activity.MainActivity"
android:exported="false">
</activity>
<activity android:name="com.sw.platecabinet.activity.FunActivity" />
</application>
</manifest>
@@ -14,6 +14,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
@@ -417,8 +418,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
// private val initialDelay = 5 * 60 * 1000L
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 5 * 60 * 1000L
private val dealyMillis = 5 * 60 * 1000L
private val initialDelay = 1 * 1000L
private val dealyMillis = 10 * 1000L
fun startFaceTask(block: (List<String?>) -> Unit) {
faceTaskJob =
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
@@ -431,9 +432,9 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
//private var taskPageNo = 1
private fun getFaceIncrementList(pageNo: Int = 1, action:(List<String?>)-> Unit) {
val timestamp = SpTool.lastFaceTimestamp
if (timestamp == 0L) {
return
}
// if (timestamp == 0L) {
// return
// }
settingViewModel.getFaceIncrementList(
pageNo = pageNo,
timestamp = timestamp
@@ -441,6 +442,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
runOnUiThread {
if (pageNo == 1 && list.isEmpty()) {
//未查询到增量数据
// Thread{
// val faceDao = FaceDatabase.getInstance(this).faceDao()
// if (faceDao.getFaceCount() == 0) {
// FaceEntity("-1", null, byteArrayOf(-1)).also { it.userType = "-1" }
// }
// }.start()
return@runOnUiThread
}
updateFaceData(list)
@@ -462,28 +469,26 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
public val recognizeViewModel by viewModels<RecognizeViewModel>()
private fun updateFaceData(list: List<UserFaceModel>) {
Thread {
lifecycleScope.launch(Dispatchers.IO) {
val faceList = mutableListOf<FaceEntity>()
val faceDao = FaceDatabase.getInstance(this).faceDao()
val faceDao = FaceDatabase.getInstance(this@BaseActivity).faceDao()
try {
list.forEach { model ->
if (model.faceDeleted == true) {
//删除数据
faceDao.deleteFaceById(model.userId)
// if (faceDao.getFaceCount() == 0) {
// FaceEntity("-1", null, byteArrayOf(-1)).also { it.userType = "-1" }
// }
} else {
val faceData = faceDao.queryByUserName(model.userId)
if (faceData == null) {
//保存数据
val faceEntity = FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.isMember) "1" else "2"
it.cardNo = model.cardNo
}
faceList.add(faceEntity)
// 获取该用户所有已存特征,判断当前特征是否已存在
val existList = faceDao.queryAllByUserName(model.userId)
val featureMatches = existList.map { Base64.encode(it.featureData) == model.faceFeatureStr }
Timber.d("userId=${model.userId}, 已存记录数=${existList.size}, 各条特征是否与服务器一致=$featureMatches")
val alreadyExists = featureMatches.any { it }
if (!alreadyExists) {
//当前特征不存在,保存
faceList.add(getNewFaceEntity(model))
}
}
}
@@ -492,13 +497,25 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
try {
if (faceList.isNotEmpty()) {
FaceDatabase.getInstance(this).faceDao().insert(faceList)
recognizeViewModel.refreshFaceList()
FaceDatabase.getInstance(this@BaseActivity).faceDao().insert(faceList)
}
recognizeViewModel.refreshFaceList()
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
}
private fun getNewFaceEntity(model: UserFaceModel) : FaceEntity {
return FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo
}
}
fun initPlateList() {
@@ -518,6 +535,10 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll()
faceDao.resetId()
// faceDao.insert(
// FaceEntity("-1", null, byteArrayOf(-1)).also { it.userType = "-1" }
// )
recognizeViewModel.refreshFaceList()
runOnUiThread { block() }
}.start()
}
@@ -7,6 +7,7 @@ import com.sw.plate.utils.arcface.FaceApi
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.databinding.ActivityDeviceInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -48,9 +49,11 @@ class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
showWaitingDialog("加载中……")
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val count = FaceApi().queryFaceCount()
if (count == 0) {
//val count = FaceApi().queryFaceCount()
//if (count == 0) {
if (SpTool.firstGetFace) {
userViewModel.getUserFaceCache { status, msg ->
SpTool.firstGetFace = false
getDeviceConfig()
}
} else {
@@ -0,0 +1,152 @@
package com.sw.platecabinet.activity
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.databinding.ActivityFunBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.dialog.CustomDialog
import com.sw.platecabinet.ext.clickWithDebounce
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.viewmodel.UserViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.getValue
class FunActivity : BaseActivity<ActivityFunBinding>() {
override fun inflateTitleBinding(): ItemTitleTimeBinding? {
return binding.includeHeader
}
override fun inflateViewBinding() = ActivityFunBinding.inflate(layoutInflater)
override fun initialize() {
binding.btnClearFace.clickWithDebounce { clearFace() }
binding.btnClearAndGetAll.clickWithDebounce { clearFaceAndGetAll() }
binding.btnOpenAllPlate.clickWithDebounce {
lifecycleScope.launch {
openAllPlate()
}
}
binding.btnPutPlate.clickWithDebounce { putPlate() }
queryUserCount()
binding.btnTestFeature.clickWithDebounce {
testFeature()
}
}
private fun testFeature() {
userViewModel.getUserFace(1, 1) {list ->
lifecycleScope.launch {
withContext(Dispatchers.IO) {
if (list.isEmpty()) return@withContext
val userId = list[0].userId
val base64Feature = list[0].faceFeatureStr
val faceDao = FaceDatabase.getInstance(this@FunActivity).faceDao()
val entityList = faceDao.queryAllByUserName(userId)
val resultList = entityList.map { base64Feature == Base64.encode(it.featureData) }
val isEqual = resultList.any { true }
ToastUtils.showToast("比较结果:$isEqual")
}
}
}
}
private fun clearFace() {
loadClearDialog("请确认是否清除本地所有数据?") {
showWaitingDialog("加载中……")
clearAllFace {
binding.root.postDelayed(
{
hideWaitingDialog()
ToastUtils.showToast("已清除")
queryUserCount()
}, 500
)
}
}
}
private fun clearFaceAndGetAll() {
loadClearDialog("请确认是否清除本地所有数据,并重新拉取全量人脸数据?") {
showWaitingDialog("加载中……")
clearAllFace {
userViewModel.getUserFaceCache { status, msg ->
hideWaitingDialog()
ToastUtils.showToast(
if (status) "请求成功,$msg" else "请求失败,$msg"
)
queryUserCount()
}
}
}
}
private suspend fun openAllPlate() {
MyApp.plateList?.let { plateList ->
val boxCodeList = plateList.filter { it.equipmentBoxCode.isNullOrBlank().not() }
.map { it.equipmentBoxCode }
boxCodeList.forEach { boxCode ->
withContext(Dispatchers.Default) {
PlateUtils.openDirect(boxCode)
delay(500)
}
}
}
}
private fun putPlate() {
showWaitingDialog("加载中……")
MyApp.plateList?.forEachIndexed { index, item ->
val num = index + 1
val realNum = if (num < 10) "0${num}" else "${num}"
item.plateNumber = realNum + realNum + realNum
item.faceId = null
item.updateTime = null
}
SpTool.savePlateData(MyApp.plateList!!)
binding.root.postDelayed({
hideWaitingDialog()
ToastUtils.showToast("已完成")
}, 1000)
}
/**
* 加载清除数据弹窗
*/
private fun loadClearDialog(msg: String, callback: () -> Unit) {
CustomDialog(
activity = this,
content = msg,
onConfirm = {
callback()
}).show()
}
override fun onLeftDoubleClick() {
finish()
}
override fun onRightDoubleClick() {
}
private fun queryUserCount() {
lifecycleScope.launch {
val faceDao = FaceDatabase.getInstance(this@FunActivity).faceDao()
val memberUserCount = withContext(Dispatchers.IO) { faceDao.getFaceCount("1") }
val tempUserCount = withContext(Dispatchers.IO) { faceDao.getFaceCount("2") }
binding.tvMemberUserCount.text = "会员用户:${memberUserCount}"
binding.tvTempUserCount.text = "临时用户:${tempUserCount}"
}
}
}
@@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")
package com.sw.platecabinet.activity
import android.Manifest
@@ -6,14 +8,15 @@ import android.graphics.Bitmap
import android.graphics.Point
import android.hardware.Camera
import android.os.Build
import android.os.CountDownTimer
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi
import androidx.lifecycle.Observer
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.arcsoft.face.ErrorInfo
import com.bumptech.glide.Glide
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.utils.Base64
@@ -27,6 +30,7 @@ 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.constants.RecognizeColor
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
@@ -39,9 +43,13 @@ import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ActivityLoginFaceBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.ext.clickWithDebounce
import com.sw.platecabinet.ext.gone
import com.sw.platecabinet.ext.visible
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.BitmapSaver
import com.sw.platecabinet.utils.CountDownUtil
import com.sw.platecabinet.utils.Debouncer
import com.sw.platecabinet.utils.LogFileUtil
import com.sw.platecabinet.utils.PermissionHelper
@@ -49,6 +57,9 @@ import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.RegisterCallbackHandler
import com.sw.platecabinet.utils.SoundPoolUtil
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -61,8 +72,6 @@ import java.util.concurrent.atomic.AtomicBoolean
*/
class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
ViewTreeObserver.OnGlobalLayoutListener {
// private val recognizeViewModel by viewModels<RecognizeViewModel>()
private var countDownTimer: CountDownTimer? = null
private val CAMERA_PERMISSION_REQUEST_CODE = 100
private val REQUIRED_PERMISSIONS: Array<String> = arrayOf(
@@ -81,64 +90,22 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
companion object {
private const val TAG = "LoginByFaceActivity"
private var instance: LoginByFaceActivity? = null
private const val COLLECT_SUCCESS = "collect_success"
private const val RECOGNIZE_SUCCESS = "recognize_success"
private const val NO_PLATE_REMIND = "no_plate_remind";
fun goInitActivity() {
instance?.goInitActivity()
}
private const val NO_PLATE_REMIND = "no_plate_remind"
}
// private var logList: MutableList<String> = mutableListOf()
// private val logAdapter by lazy { LogAdapter(logList) }
override fun initialize() {
instance = this
userViewModel.activeEngine()
// addSocketListener()
initCountTime()
initArcViewModel()
initArcView()
openRectInfoDraw = true
recognizeViewModel.setDrawRectInfoTextValue(true)
//viewModel.generateToken()
// binding.llToPwd.setOnClickListener {
// val intent = Intent(this, LoginByPwdActivity::class.java)
// startActivity(intent)
// }
// TaskManager.startTask()
EventBus.getDefault().register(this)
// binding.btnCollectFace.setOnClickListener { collectFace() }
// binding.rvLogInfo.let {
// it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
// it.adapter = logAdapter
// }
//
// binding.btnLogState.let {
// it.tag = false
// it.setImageResource(R.drawable.ic_log_show)
// it.setOnClickListener { v ->
// binding.rvLogInfo.run {
// val state = it.tag.toString().toBooleanStrictOrNull() ?: false
// if (state) visible() else gone()
// }
// val state = it.tag.toString().toBooleanStrictOrNull() ?: false
// it.tag = state
// it.setImageResource(
// if (state) R.drawable.ic_log_hide else R.drawable.ic_log_show
// )
// }
// }
//开启人脸增量数据定时任务
startFaceTask {
loadLogInfo("collectFace增量接口数据用户id:${Gson().toJson(it)}")
@@ -148,7 +115,22 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
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);
SoundPoolUtil.getInstance().loadR(this, soundMap)
binding.btnCollect.clickWithDebounce {
//先重置,后采集
resetRecognizeState()
//采集数据按钮
retryFailCount = 0
isCollectFace = true
isCollecting.set(true)
collectFace()
}
binding.btnRetry.clickWithDebounce {
//重新识别按钮
resetRecognizeState()
}
}
@@ -184,29 +166,52 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private var rgbFaceRectTransformer: FaceRectTransformer? = null
private val livenessType = LivenessType.RGB
private var openRectInfoDraw = false
private val regDebouncer = Debouncer(3000)
private val leftDebouncer = Debouncer(30 * 1000)
/** 识别超时倒计时(15秒无操作跳回初始页) */
private val recognizeCountDownUtil = CountDownUtil()
/** 用户离开画面后的倒计时(llCollectButton 显示状态下无人脸时触发) */
private val leftCountDownUtil = CountDownUtil()
private var registerHandler: RegisterCallbackHandler? = null
private fun isFitData(userFaceInfo: UserFaceInfo?): Boolean {
if (userFaceInfo == null) return false
val headBmp = userFaceInfo.headBmp
if (headBmp == null) return false
// if (headBmp.width <= 200 || headBmp.height <= 200) {
// loadLogInfo("collectFace,isFitData:拿到人脸提取特征数据照片过小-----------")
// return false
// }
return true
}
private fun initArcViewModel() {
recognizeViewModel.setLiveType(livenessType)
recognizeViewModel.prepareRegister()
recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo ->
registerHandler?.setCallback {
if (!isCollectFace) {
if (!isCollectFace || !isCollecting.get()) {
return@setCallback
}
if (this.userFaceInfo == null && userFaceInfo != null) {
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
sendFaceData(faceData, headBmp)
}
getFaceDataBlock()
}
}
}
recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? ->
recognizeViewModel.ftInitCode.observe(this) { ftInitCode: Int? ->
if (ftInitCode != ErrorInfo.MOK) {
val error: String? = getString(
R.string.specific_engine_init_failed, "ftEngine",
@@ -215,8 +220,8 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
Timber.e("ftInitCode observe = $error")
ToastUtils.showToast(error)
}
})
recognizeViewModel.frInitCode.observe(this, Observer { frInitCode: Int? ->
}
recognizeViewModel.frInitCode.observe(this) { frInitCode: Int? ->
if (frInitCode != ErrorInfo.MOK) {
val error: String? = getString(
R.string.specific_engine_init_failed, "frEngine",
@@ -225,8 +230,8 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
Timber.e("frInitCode observe = $error")
ToastUtils.showToast(error)
}
})
recognizeViewModel.flInitCode.observe(this, Observer { flInitCode: Int? ->
}
recognizeViewModel.flInitCode.observe(this) { flInitCode: Int? ->
if (flInitCode != ErrorInfo.MOK) {
val error: String? = getString(
R.string.specific_engine_init_failed, "flEngine",
@@ -235,35 +240,26 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
Timber.e("flInitCode observe = $error")
ToastUtils.showToast(error)
}
})
}
recognizeViewModel.recognizeConfiguration
.observe(this, Observer { recognizeConfiguration: RecognizeConfiguration? ->
.observe(this) { recognizeConfiguration: RecognizeConfiguration? ->
Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}")
})
recognizeViewModel.recognizeNotice.observe(this, Observer { notice: String? ->
Timber.i("recognizeNotice observe notice = $notice")
})
recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? ->
loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}")
if (faceUseSwitch) {
loadFaceRecognizeResult2(result)
return@Observer
}
loadFaceRecognizeResult(result)
})
recognizeViewModel.recognizeNotice.observe(this) { notice: String? ->
Timber.i("recognizeNotice observe notice = $notice")
}
recognizeViewModel.drawRectInfoText.observe(this, Observer { info ->
recognizeViewModel.recognizeUserId.observe(this) { result: CompareResult? ->
loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}")
loadFaceRecognizeResult3(result)
}
recognizeViewModel.drawRectInfoText.observe(this) { info ->
Timber.i("drawRectInfoText observe info = $info")
})
}
}
/**
* 人脸识别开关,true-loadFaceRecognizeResult2false-loadFaceRecognizeResult
*/
private var faceUseSwitch = true
/**
* 识别失败重试次数
*/
@@ -274,65 +270,33 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
*/
private val maxFailCount = 5
/**
* 人脸失败阈值
*/
private val faceFailThreshold = 0.3
/**
* 配置阈值,默认值为0.8
*/
private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) }
private val openPlateDebouncer = Debouncer(5000)
private val isCollecting = AtomicBoolean(false)
/**
* 根据相似度similar>设定阈值0.8作为识别成功判断,
* 如果similar<0.3作为识别失败判断,
* 如果similar>=0.3&&similar<=0.8时,暂时不作为失败情况,再识别5次如果similar<0.8则作为识别失败处理
*/
private fun loadFaceRecognizeResult2(result: CompareResult?) {
// 无有效结果直接忽略
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"
"collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, 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)
}
recognizeSuccess(userId)
return
}
if (isCollectFace) {
if (isCollectFace || isCollecting.get()) {
loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar")
return
}
if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) {
if (retryFailCount >= maxFailCount) {
loadLogInfo(
"collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " +
"retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}"
@@ -342,9 +306,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
return
}
retryFailCount = 0
isCollectFace = true
collectFace()//采集人脸
recognizeFail()
return
}
@@ -352,57 +314,53 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount")
}
// private fun loadFaceRecognizeResult2(result: CompareResult?) {
//
// if(isCollectFace){
// return
// }
//
// val similar = result?.similar ?: 0f
// binding.tvSimilarValue.text = "$similar${retryFailCount}次"
// loadLogInfo("collectFace,recognizeUserId 人脸识别配置阈值为:${faceSuccessThreshold},失败阈值为:${faceFailThreshold},相似度为:${similar},失败次数为:${retryFailCount}")
// if (similar > faceSuccessThreshold) {
// loadLogInfo("collectFace,recognizeUserId 相似度为:${similar},识别成功时间:${DateTimeUtils.getDateTimeString()}")
// //大 于识别阈值,作为识别成功判断
// currentUserId = result?.faceEntity?.userName
// Debouncer(5000).debounce { openPlate(currentUserId) }
// return
// }
// loadLogInfo("collectFace,recognizeUserId 人脸识别相似度为:${similar},失败次数为:${retryFailCount}")
// if (similar < faceFailThreshold || retryFailCount >= maxFailCount) {
// //相似度低于识别错误阈值进行采集、识别失败次数大于等于最大失败次数,作为识别失败判断
// if (isCollectFace.not()) {
// //非采集中
// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,相似度为:${similar},开始采集时间:${DateTimeUtils.getDateTimeString()}")
// isCollectFace = true
// collectFace()
// return
// }
// }
// //满足similar >= faceFailThreshold && similar <= successThreshold时进行重试
// retryFailCount++
// loadLogInfo("collectFace,recognizeUserId 人脸识别失败次数为:${retryFailCount}")
// }
/**
* 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果
*/
private fun loadFaceRecognizeResult(result: CompareResult?) {
val similar = result?.similar ?: 0f
binding.tvSimilarValue.text = "$similar"
val similarPass = result?.isSimilarPass ?: false
if (similarPass.not()) {
loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,开始采集")
isCollectFace = true
collectFace()
private fun recognizeSuccess(userId: String?) {
if (userId.isNullOrBlank()) {
loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略")
return
}
currentUserId = result?.faceEntity?.userName
if (tempUserId != currentUserId) {
SoundPoolUtil.getInstance().play("success", 0)
loadLogInfo(
"collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}"
)
// 成功后必须重置状态
retryFailCount = 0
isCollecting.set(false)
isCollectFace = false
currentUserId = userId
// 防抖,防止重复开闸
openPlateDebouncer.debounce {
openPlate(userId)
}
// plateEnable = true
Debouncer(5000).debounce { openPlate(currentUserId) }
}
private fun recognizeFail() {
recognizeCountDownUtil.cancelCountDown()
binding.tvToInit.text = ""
binding.tvSimilarValue.text = ""
binding.llCollectButton.visible()
binding.btnRetry.visible()
binding.btnCollect.run {
visible()
text = "新用户采集"
}
binding.ivCollectPhoto.visible()
}
private fun newUserCollect() {
recognizeCountDownUtil.cancelCountDown()
binding.tvToInit.text = ""
binding.tvSimilarValue.text = ""
binding.llCollectButton.visible()
binding.btnRetry.gone()
binding.btnCollect.run {
visible()
text = "新用户采集"
}
binding.ivCollectPhoto.gone()
resumeCamera()
}
/**
@@ -446,9 +404,12 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
item.updateTime = DateTimeUtils.getDateTimeString()
item.plateNumber = null
plateEnable = false
Debouncer(5000).debounce {
openDebouncer.debounce {
//打开柜子
PlateUtils.open(this, item.equipmentBoxCode, false)
PlateUtils.open(this, item.equipmentBoxCode, false) {
goInitActivity()
}
recognizeCountDownUtil.cancelCountDown()
}
currentPlateInfo = item
MyApp.plateList = items
@@ -456,21 +417,19 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
}
}
// private val debouncer by lazy { Debouncer(3000) }
private val openDebouncer by lazy { Debouncer(5000) }
private fun initArcView() {
//在布局结束后才做初始化操作
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
recognizeViewModel.getCompareResultList().getValue()
binding.dualCameraTexturePreviewRgb.viewTreeObserver.addOnGlobalLayoutListener(this)
val list = recognizeViewModel.getCompareResultList().getValue()
loadLogInfo("initArcView:list.size=${list?.size}")
}
override fun onDestroy() {
loadLogInfo("onDestroy")
instance = null
if (rgbCameraHelper != null) {
rgbCameraHelper!!.release()
rgbCameraHelper = null
}
rgbCameraHelper?.release()
rgbCameraHelper = null
recognizeViewModel.destroy()
@@ -513,7 +472,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
layoutParams.width = (measuredHeight * ratio).toInt()
}
if (scale < 1f) {
val rgbParam = rgbPreview.getLayoutParams()
val rgbParam = rgbPreview.layoutParams
layoutParams.width = (rgbParam.width * scale).toInt()
layoutParams.height = (rgbParam.height * scale).toInt()
} else {
@@ -549,12 +508,13 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
isMirror: Boolean
) {
loadLogInfo("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
runOnUiThread({
val previewSizeRgb = camera.getParameters().getPreviewSize()
runOnUiThread {
val previewSizeRgb = camera.parameters.previewSize
val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView,
previewSizeRgb, displayOrientation, 0.6f
previewSizeRgb, displayOrientation, 0.7f
//0.6f
)
loadLogInfo("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
loadLogInfo("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
@@ -583,7 +543,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
recognizeViewModel.onRgbCameraOpened(camera)
recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer)
})
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
@@ -600,6 +560,13 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
currentPlateInfo = null
currentUserId = null
userFaceInfo = null
binding.btnCollect.tag = ""
binding.tvSimilarValue.text = ""
retryFailCount = 0
if (binding.llCollectButton.isVisible) {
leftDebouncer.debounce { leftCountDown() }
}
}
recognizeViewModel.clearLeftFace(facePreviewInfoList)
}
@@ -638,8 +605,8 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
.previewOn(binding.dualCameraTexturePreviewRgb)
.cameraListener(cameraListener)
.build()
rgbCameraHelper!!.init()
rgbCameraHelper!!.start()
rgbCameraHelper?.init()
rgbCameraHelper?.start()
}
/**
@@ -657,94 +624,160 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
}
}
fun openRectInfoDraw(view: View?) {
openRectInfoDraw = !openRectInfoDraw
recognizeViewModel.setDrawRectInfoTextValue(openRectInfoDraw)
}
// fun openRectInfoDraw(view: View?) {
// openRectInfoDraw = !openRectInfoDraw
// recognizeViewModel.setDrawRectInfoTextValue(openRectInfoDraw)
// }
override fun onGlobalLayout() {
loadLogInfo("onGlobalLayout")
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
binding.dualCameraTexturePreviewRgb.viewTreeObserver.removeOnGlobalLayoutListener(this)
// 请求权限
checkCameraPermission()
}
private fun resumeCamera() {
isRecognition = true
// isRecognition = true
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) {
loadLogInfo("collectFace,resumeCamera: ")
isRecognition = true
rgbCameraHelper!!.start()
} else {
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
//recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
recognizeViewModel.onPreviewFrame(emptyFrame, true)
binding.dualCameraFaceRectView.clearFaceInfo()
recognizeViewModel.clearLeftFace(facePreviewInfoList)
binding.root.postDelayed({
isRecognition = true
}, 500)
}
}
/**
* 临时用户id
* 构造符合分辨率的 NV21 格式全黑帧
* @param width 图像宽度(如 1920
* @param height 图像高度(如 1080
* @return 合法的全黑帧字节数组
*/
private var tempUserId: String = ""
private fun createBlackNV21Frame(width: Int, height: Int): ByteArray {
val frameSize = width * height
val nv21Data = ByteArray(frameSize * 3 / 2) // NV21 格式长度 = 宽×高×1.5
// Y 分量(亮度)设为 0(全黑),UV 分量设为 128(默认值)
// 1. 填充 Y 分量(前 frameSize 个字节)
// Arrays.fill(nv21Data, 0, frameSize, 0.toByte())
nv21Data.fill(0, 0, frameSize)
// 2. 填充 UV 分量(后 frameSize/2 个字节)
// Arrays.fill(nv21Data, frameSize, nv21Data.size, 128.toByte())
nv21Data.fill(128.toByte(), frameSize, nv21Data.size)
return nv21Data
}
// 调用示例:构造 1920×1080 的全黑帧(长度 1382400
// private val emptyFrame = createBlackNV21Frame(1920, 1080)
private val emptyFrame = createBlackNV21Frame(1280, 720)
// 传递“空帧” recognizeViewModel.onPreviewFrame(emptyFrame, true)
// /**
// * 临时用户id
// */
// private var tempUserId: String = ""
override fun onResume() {
super.onResume()
registerHandler = RegisterCallbackHandler()
try {
if (isFirstOpen) {
isFirstOpen = false
goInitActivity()
binding.root.postDelayed({
isFirstOpen = false
}, 1000)
return
}
retryFailCount = 0
binding.tvSimilarValue.text = ""
isCollecting.set(false)
isCollectFace = false
plateEnable = true
collectCount = 0
tempUserId = "${System.currentTimeMillis()}"
resumeCamera()
userViewModel.resetUserInfo()
countDownTimer?.let {
it.cancel()
it.start()
lifecycleScope.launch {
try {
val faceDao = FaceDatabase.getInstance(this@LoginByFaceActivity).faceDao()
val faceCount = withContext(Dispatchers.IO) { faceDao.faceCount }
if (faceCount == 0) {
newUserCollect()
} else {
resetRecognizeState()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// initFaceScheduler()
// checkFaceState()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 重置识别状态
*/
private fun resetRecognizeState() {
binding.ivCollectPhoto.gone()
binding.ivCollectPhoto.setImageDrawable(null)
binding.tvToInit.text = "15"
binding.llCollectButton.gone()
retryFailCount = 0
binding.tvSimilarValue.text = ""
isCollecting.set(false)
isCollectFace = false
plateEnable = true
collectCount = 0
// tempUserId = "${System.currentTimeMillis()}"
resumeCamera()
registerHandler = RegisterCallbackHandler()
recognizeCountDownUtil.startCountDown(
total = 15,
lifecycleScope = lifecycleScope,
onTick = { remaining ->
loadLogInfo("resetRecognizeState剩余时间:$remaining")
if (judgeRecognizeState()) return@startCountDown
loadLogInfo("initCountTime onTick = $remaining")
binding.tvToInit.text = "$remaining"
},
onFinish = {
loadLogInfo("resetRecognizeState倒计时结束")
if (judgeRecognizeState()) return@startCountDown
if (facePreviewInfoList.isNullOrEmpty()) {
goInitActivity()
} else {
loadLogInfo("recognizeFail---initCountTime")
recognizeFail()
}
}
)
}
private var isFirstOpen = true
protected override fun onPause() {
override fun onPause() {
pauseCamera()
super.onPause()
countDownTimer?.cancel()
recognizeCountDownUtil.cancelCountDown()
}
private fun pauseCamera() {
isRecognition = false
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
// recognizeViewModel.onPreviewFrame(emptyFrame, true)
}
override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
super.handleLoginSuccess(equipmentUserInfo, isAdmin)
// goInitActivity()
}
private val totalTimeInMillis = 15 * 1000L
fun initCountTime() {
countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) {
loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}")
binding.tvToInit.text = "${(millisUntilFinished / 1000).toInt() + 1}"
}
override fun onFinish() {
goInitActivity()
private fun judgeRecognizeState(): Boolean {
val rectColor = binding.dualCameraFaceRectView.rectColor
if (rectColor == RecognizeColor.COLOR_SUCCESS && currentUserId.isNullOrBlank()
.not() && facePreviewInfoList.isNullOrEmpty().not()
) {
//人未离开,再次识别
loadLogInfo("userId=$currentUserId")
openPlateDebouncer.debounce {
openPlate(currentUserId)
}
return true
}
return false
}
fun goInitActivity() {
@@ -785,12 +818,6 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
uploadFaceDebouncer.debounce {
addUserFace(faceData, imageFile)
}
// // 给某个客户端发送
// val jsonObject = JSONObject().also {
// it.put("type", "faceFeature")
// it.put("content", faceData)
// }
// tcpClient?.send(jsonObject)
}
private fun addUserFace(faceData: String, imageFile: File) {
@@ -806,23 +833,34 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据成功但接口返回为null,faceData=${faceData}")
return@addUserFace
}
Thread {
saveFaceInfo(faceModel)
lifecycleScope.launch(Dispatchers.IO) {
saveFaceInfo(faceModel) {
runOnUiThread {
Glide.with(this@LoginByFaceActivity).load(imageFile)
.into(binding.ivCollectPhoto)
}
}
uploadFaceDebouncer.reset()
}.start()
}
}
}
private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) {
if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) {
private fun getFaceDataBlock() {
if (userFaceInfo?.faceFeature?.featureData == null) {
loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据为null-----------")
return
}
val faceData: String? = Base64.encode(userFaceInfo?.faceFeature?.featureData)
if (faceData.isNullOrBlank()) {
loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据Base64编码为空")
return
}
loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------")
block(faceData, userFaceInfo!!.headBmp)
val headBmp = userFaceInfo!!.headBmp
//block(faceData, headBmp)
isCollecting.set(false)
isCollectFace = false
sendFaceData(faceData, headBmp)
}
/**
@@ -839,7 +877,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private var currentUserId: String? = null
private fun saveFaceInfo(faceModel: UserFaceModel) {
private fun saveFaceInfo(faceModel: UserFaceModel, block: () -> Unit = {}) {
try {
loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------")
val faceDao = FaceDatabase.getInstance(this).faceDao()
@@ -849,15 +887,21 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
Base64.decode(faceModel.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (faceModel.isMember) "1" else "2"
it.userType = if (faceModel.member) "1" else "2"
}
faceDao.insert(faceEntity)
val faceId = faceDao.insert(faceEntity)
faceEntity.faceId = faceId
loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------")
recognizeViewModel.refreshFaceList()
loadLogInfo("collectFace,saveFaceInfo: ----------11,刷新人脸数据-----------")
runOnUiThread { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) }
runOnUiThread {
//采集完成,不再识别,直接提示成功
isRecognition = false
recognizeSuccess("${System.currentTimeMillis()}")
}
loadLogInfo("---------------------------------")
collectCount = 0
block()
} catch (e: Exception) {
e.printStackTrace()
loadLogInfo("collectFace,saveFaceInfo: ----------13,人脸采集异常:${e.message}-----------")
@@ -869,78 +913,23 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private var collectCount = 0
// private var tcpClient: TcpClient? = null
//
// private fun addSocketListener() {
//// val ipAddress = NetworkUtils.getIPAddress(true)
//// loadLogInfo( "addSocketListener,ipAddress: $ipAddress");
// tcpClient = TcpClient(
// "192.168.1.95",
// 5000,
// GlobalData.deviceId, // clientId
// 5000, // connectTimeoutMs
// 10 * 1000, // heartbeatIntervalMs
// 30 * 1000 // heartbeatTimeoutMs
// )
//
// tcpClient?.setListener(object : TcpClientListenerImpl() {
// override fun onSendSuccess(json: JSONObject?) {
// super.onSendSuccess(json)
// loadLogInfo("collectFace,TcpClientListener发送成功:$json")
//// toast("发送成功")
// }
//
// override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) {
// super.onSendFailed(json, e)
// ToastUtils.showToast("发送失败${e?.message}")
// loadLogInfo("collectFace,TcpClientListener人脸数据发送失败:${e?.message}")
// }
//
// override fun onMessage(json: JSONObject?) {
// super.onMessage(json)
// loadLogInfo("collectFace,TcpClientListener收到消息:$json")
// val type = json?.getInt("type")
// // 根据服务端消息类型处理删除新增临时用户人脸数据---------------------
// when (type) {
// LanServer.TYPE_ADD_FACE_DATA -> {
// //采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据
// loadLogInfo("collectFace,TcpClientListener收到消息,新增用户:采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据")
// }
//
// LanServer.TYPE_CLEAR_FACE_DATA -> {
// clearFaceData()
// }
//
// else -> {}
// }
// }
// })
// tcpClient?.start()
//
// try {
// LanServer.getInstance().let {
// it.setListener(object : LanServerListenerImpl() {
// override fun onMessageReceived(clientId: String?, message: JSONObject?) {
// super.onMessageReceived(clientId, message)
// ToastUtils.showToast("收到消息clientId=$clientId")
// }
// })
// it.start()
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
private fun clearFaceData() {
Thread {
val faceDao = FaceDatabase.getInstance(this).faceDao()
faceDao.deleteTempUserFaceData()
recognizeViewModel.refreshFaceList()
loadLogInfo("collectFace,TcpClientListener收到消息,已删除并刷新人脸数据")
}.start()
}
private var isCollectFace = false
private fun leftCountDown() {
leftCountDownUtil.startCountDown(total = 15, lifecycleScope = lifecycleScope, onStart = {
loadLogInfo("onPreview-----------leftCountDown-------------")
}, onTick = {
loadLogInfo("leftCountDown.onTick:$it")
if (facePreviewInfoList.isNullOrEmpty().not()) {
loadLogInfo("leftCountDown.onTick-cancel()")
leftCountDownUtil.cancelCountDown()
return@startCountDown
}
}, onFinish = {
if (facePreviewInfoList.isNullOrEmpty().not()) {
loadLogInfo("leftCountDown.onFinish-人脸未离开,不跳转")
return@startCountDown
}
goInitActivity()
})
}
}
@@ -0,0 +1,946 @@
//package com.sw.platecabinet.activity
//
//import android.Manifest
//import android.content.Intent
//import android.graphics.Bitmap
//import android.graphics.Point
//import android.hardware.Camera
//import android.os.Build
//import android.os.CountDownTimer
//import android.util.DisplayMetrics
//import android.view.View
//import android.view.ViewGroup
//import android.view.ViewTreeObserver
//import androidx.annotation.RequiresApi
//import androidx.lifecycle.Observer
//import com.arcsoft.face.ErrorInfo
//import com.google.gson.Gson
//import com.sw.inbound.utils.DateTimeUtils
//import com.sw.plate.utils.Base64
//import com.sw.plate.utils.ToastUtils
//import com.sw.plate.utils.arcface.ConfigUtil
//import com.sw.plate.utils.arcface.ErrorCodeUtil
//import com.sw.plate.utils.arcface.FaceRectTransformer
//import com.sw.plate.utils.arcface.FaceRectView
//import com.sw.plate.utils.arcface.FaceRectView.DrawInfo
//import com.sw.plate.utils.arcface.PreviewConfig
//import com.sw.plate.utils.arcface.camera.CameraListener
//import com.sw.plate.utils.arcface.camera.DualCameraHelper
//import com.sw.plate.utils.arcface.face.constants.LivenessType
//import com.sw.plate.utils.arcface.face.model.CompareResult
//import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
//import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
//import com.sw.plate.utils.arcface.facedb.FaceDatabase
//import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
//import com.sw.plate.utils.arcface.model.UserFaceInfo
//import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
//import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY
//import com.sw.platecabinet.MyApp
//import com.sw.platecabinet.R
//import com.sw.platecabinet.databinding.ActivityLoginFaceBinding
//import com.sw.platecabinet.databinding.ItemTitleTimeBinding
//import com.sw.platecabinet.model.response.EquipmentUserInfo
//import com.sw.platecabinet.model.response.UserFaceModel
//import com.sw.platecabinet.utils.BitmapSaver
//import com.sw.platecabinet.utils.Debouncer
//import com.sw.platecabinet.utils.LogFileUtil
//import com.sw.platecabinet.utils.PermissionHelper
//import com.sw.platecabinet.utils.PlateUtils
//import com.sw.platecabinet.utils.RegisterCallbackHandler
//import com.sw.platecabinet.utils.SoundPoolUtil
//import com.sw.platecabinet.utils.SpTool
//import org.greenrobot.eventbus.EventBus
//import org.greenrobot.eventbus.Subscribe
//import org.greenrobot.eventbus.ThreadMode
//import timber.log.Timber
//import java.io.File
//import java.util.concurrent.atomic.AtomicBoolean
//
///**
// * 人脸识别
// */
//class LoginByFaceActivityBak : BaseActivity<ActivityLoginFaceBinding>(),
// ViewTreeObserver.OnGlobalLayoutListener {
// // private val recognizeViewModel by viewModels<RecognizeViewModel>()
// private var countDownTimer: CountDownTimer? = null
//
// private val CAMERA_PERMISSION_REQUEST_CODE = 100
// private val REQUIRED_PERMISSIONS: Array<String> = arrayOf(
// Manifest.permission.CAMERA,
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
// Manifest.permission.READ_EXTERNAL_STORAGE
// )
//
// override fun inflateViewBinding(): ActivityLoginFaceBinding {
// return ActivityLoginFaceBinding.inflate(layoutInflater)
// }
//
// override fun inflateTitleBinding(): ItemTitleTimeBinding {
// return binding.includeHeader
// }
//
// companion object {
// private const val TAG = "LoginByFaceActivity"
// private var instance: LoginByFaceActivityBak? = null
//
// private const val COLLECT_SUCCESS = "collect_success"
// private const val RECOGNIZE_SUCCESS = "recognize_success"
// private const val NO_PLATE_REMIND = "no_plate_remind";
//
// fun goInitActivity() {
// instance?.goInitActivity()
// }
// }
//
//// private var logList: MutableList<String> = mutableListOf()
//// private val logAdapter by lazy { LogAdapter(logList) }
//
// override fun initialize() {
// instance = this
// userViewModel.activeEngine()
//// addSocketListener()
// initCountTime()
// initArcViewModel()
// initArcView()
// openRectInfoDraw = true
// recognizeViewModel.setDrawRectInfoTextValue(true)
// //viewModel.generateToken()
//
//// binding.llToPwd.setOnClickListener {
//// val intent = Intent(this, LoginByPwdActivity::class.java)
//// startActivity(intent)
//// }
//
//// TaskManager.startTask()
//
// EventBus.getDefault().register(this)
//
//// binding.btnCollectFace.setOnClickListener { collectFace() }
//
//
//// binding.rvLogInfo.let {
//// it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
//// it.adapter = logAdapter
//// }
////
//// binding.btnLogState.let {
//// it.tag = false
//// it.setImageResource(R.drawable.ic_log_show)
//// it.setOnClickListener { v ->
//// binding.rvLogInfo.run {
//// val state = it.tag.toString().toBooleanStrictOrNull() ?: false
//// if (state) visible() else gone()
//// }
//// val state = it.tag.toString().toBooleanStrictOrNull() ?: false
//// it.tag = state
//// it.setImageResource(
//// if (state) R.drawable.ic_log_hide else R.drawable.ic_log_show
//// )
//// }
//// }
//
// //开启人脸增量数据定时任务
// startFaceTask {
// loadLogInfo("collectFace增量接口数据用户id:${Gson().toJson(it)}")
// }
//
// val soundMap = hashMapOf<String, Int>()
// 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);
// }
//
//
// private fun checkCameraPermission() {
// Timber.i("checkCameraPermission")
// val permissionHelper =
// PermissionHelper.with(this, REQUIRED_PERMISSIONS, CAMERA_PERMISSION_REQUEST_CODE)
// .onGranted {
// loadLogInfo("checkCameraPermission 权限已允许")
// // 权限已授予
// recognizeViewModel.init()
// initRgbCamera()
// resumeCamera()
// }
// .build()
// registerPermissionHelper(permissionHelper)
// permissionHelper.checkAndRequest()
// }
//
// @Subscribe(threadMode = ThreadMode.MAIN)
// fun getFaceEntity(insertEntity: FaceEntity) {
// Timber.tag("performSync").e("-time=%s", insertEntity.registerTime)
// recognizeViewModel.addFace(insertEntity)
// Timber.tag("performSync over").e("-time=%s", insertEntity.registerTime)
// }
//
// override fun onLeftDoubleClick() {
// finish()
// }
//
// private var isRecognition = false
// private var rgbCameraHelper: DualCameraHelper? = null
// private var rgbFaceRectTransformer: FaceRectTransformer? = null
// private val livenessType = LivenessType.RGB
// private var openRectInfoDraw = false
// private val regDebouncer = Debouncer(3000)
// private var registerHandler: RegisterCallbackHandler? = null
//
// private fun initArcViewModel() {
// recognizeViewModel.setLiveType(livenessType)
// recognizeViewModel.prepareRegister()
// recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo ->
// registerHandler?.setCallback {
// if (!isCollectFace) {
// return@setCallback
// }
// if (this.userFaceInfo == null && userFaceInfo != null) {
// recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE)
// this.userFaceInfo = userFaceInfo
// getFaceDataBlock { faceData, headBmp ->
// isCollecting.set(false)
// isCollectFace = false
// sendFaceData(faceData, headBmp)
// }
// }
// }
// }
// recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? ->
// if (ftInitCode != ErrorInfo.MOK) {
// val error: String? = getString(
// R.string.specific_engine_init_failed, "ftEngine",
// ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!)
// )
// Timber.e("ftInitCode observe = $error")
// ToastUtils.showToast(error)
// }
// })
// recognizeViewModel.frInitCode.observe(this, Observer { frInitCode: Int? ->
// if (frInitCode != ErrorInfo.MOK) {
// val error: String? = getString(
// R.string.specific_engine_init_failed, "frEngine",
// frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!)
// )
// Timber.e("frInitCode observe = $error")
// ToastUtils.showToast(error)
// }
// })
// recognizeViewModel.flInitCode.observe(this, Observer { flInitCode: Int? ->
// if (flInitCode != ErrorInfo.MOK) {
// val error: String? = getString(
// R.string.specific_engine_init_failed, "flEngine",
// flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!)
// )
// Timber.e("flInitCode observe = $error")
// ToastUtils.showToast(error)
// }
// })
//
// recognizeViewModel.recognizeConfiguration
// .observe(this, Observer { recognizeConfiguration: RecognizeConfiguration? ->
// Timber.i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}")
// })
// recognizeViewModel.recognizeNotice.observe(this, Observer { notice: String? ->
// Timber.i("recognizeNotice observe notice = $notice")
// })
//
// recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? ->
// loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}")
// if (faceUseSwitch) {
// loadFaceRecognizeResult2(result)
// return@Observer
// }
// loadFaceRecognizeResult(result)
// })
//
// recognizeViewModel.drawRectInfoText.observe(this, Observer { info ->
// Timber.i("drawRectInfoText observe info = $info")
// })
// }
//
// /**
// * 人脸识别开关,true-loadFaceRecognizeResult2false-loadFaceRecognizeResult
// */
// private var faceUseSwitch = true
//
// /**
// * 识别失败重试次数
// */
// private var retryFailCount = 0
//
// /**
// * 识别失败最大次数
// */
// private val maxFailCount = 5
//
// /**
// * 人脸失败阈值
// */
// private val faceFailThreshold = 0.3
//
// /**
// * 配置阈值,默认值为0.8
// */
// private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) }
//
//
// private val openPlateDebouncer = Debouncer(5000)
// private val isCollecting = AtomicBoolean(false)
//
// /**
// * 根据相似度similar>设定阈值0.8作为识别成功判断,
// * 如果similar<0.3作为识别失败判断,
// * 如果similar>=0.3&&similar<=0.8时,暂时不作为失败情况,再识别5次如果similar<0.8则作为识别失败处理
// */
// private fun loadFaceRecognizeResult2(result: CompareResult?) {
// // 无有效结果直接忽略
// val similar = result?.similar ?: return
//
// binding.tvSimilarValue.text = "$similar${retryFailCount}次"
// loadLogInfo(
// "collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " +
// "fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount"
// )
//
// if (similar >= faceSuccessThreshold) {
// val userId = result.faceEntity?.userName
// if (userId.isNullOrBlank()) {
// loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略")
// return
// }
//
// loadLogInfo(
// "collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}"
// )
//
// // 成功后必须重置状态
// retryFailCount = 0
// isCollecting.set(false)
// isCollectFace = false
// currentUserId = userId
//
// // 防抖,防止重复开闸
// openPlateDebouncer.debounce {
// openPlate(userId)
// }
// return
// }
//
// if (isCollectFace) {
// loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar")
// return
// }
//
// if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) {
// loadLogInfo(
// "collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " +
// "retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}"
// )
// if (!isCollecting.compareAndSet(false, true)) {
// loadLogInfo("collectFace 已在采集中,忽略重复触发")
// return
// }
//
// retryFailCount = 0
// isCollectFace = true
// collectFace()//采集人脸
// return
// }
//
// retryFailCount++
// loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount")
// }
//
//// private fun loadFaceRecognizeResult2(result: CompareResult?) {
////
//// if(isCollectFace){
//// return
//// }
////
//// val similar = result?.similar ?: 0f
//// binding.tvSimilarValue.text = "$similar${retryFailCount}次"
//// loadLogInfo("collectFace,recognizeUserId 人脸识别配置阈值为:${faceSuccessThreshold},失败阈值为:${faceFailThreshold},相似度为:${similar},失败次数为:${retryFailCount}")
//// if (similar > faceSuccessThreshold) {
//// loadLogInfo("collectFace,recognizeUserId 相似度为:${similar},识别成功时间:${DateTimeUtils.getDateTimeString()}")
//// //大 于识别阈值,作为识别成功判断
//// currentUserId = result?.faceEntity?.userName
//// Debouncer(5000).debounce { openPlate(currentUserId) }
//// return
//// }
//// loadLogInfo("collectFace,recognizeUserId 人脸识别相似度为:${similar},失败次数为:${retryFailCount}")
//// if (similar < faceFailThreshold || retryFailCount >= maxFailCount) {
//// //相似度低于识别错误阈值进行采集、识别失败次数大于等于最大失败次数,作为识别失败判断
//// if (isCollectFace.not()) {
//// //非采集中
//// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,相似度为:${similar},开始采集时间:${DateTimeUtils.getDateTimeString()}")
//// isCollectFace = true
//// collectFace()
//// return
//// }
//// }
//// //满足similar >= faceFailThreshold && similar <= successThreshold时进行重试
//// retryFailCount++
//// loadLogInfo("collectFace,recognizeUserId 人脸识别失败次数为:${retryFailCount}")
//// }
//
// /**
// * 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果
// */
// private fun loadFaceRecognizeResult(result: CompareResult?) {
// val similar = result?.similar ?: 0f
// binding.tvSimilarValue.text = "$similar"
// val similarPass = result?.isSimilarPass ?: false
// if (similarPass.not()) {
// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,开始采集")
// isCollectFace = true
// collectFace()
// return
// }
// currentUserId = result?.faceEntity?.userName
// if (tempUserId != currentUserId) {
// SoundPoolUtil.getInstance().play("success", 0)
// }
//// plateEnable = true
// Debouncer(5000).debounce { openPlate(currentUserId) }
// }
//
// /**
// * 查询餐盘数据
// */
// private fun getPlateData(block: (List<EquipmentUserInfo>) -> Unit) {
// MyApp.plateList?.let {
// block(it)
// }
// }
//
// private var currentPlateInfo: EquipmentUserInfo? = null
// private var plateEnable = false
//
// /**
// * 打开餐盘柜
// */
// private fun openPlate(userId: String?) {
// if (plateEnable.not()) return
// loadLogInfo("collectFace,openPlate: userId=$userId")
// getPlateData { items ->
// loadLogInfo("collectFace,openPlate: items:${Gson().toJson(items)}")
// // 未查询到当前用户,查询第一个未绑定的餐盘
// val item = items.firstOrNull { it.plateNumber.isNullOrEmpty().not() }
// loadLogInfo("collectFace,openPlate: item2:${item}")
// if (item == null) {
// //暂无餐盘
// loadLogInfo("collectFace,openPlate: 暂无餐盘,请联系管理人员,isCollectFace=$isCollectFace")
// //ToastUtils.showToast("暂无餐盘,请联系管理人员")
// SoundPoolUtil.getInstance().play(NO_PLATE_REMIND, 0)
// goInitActivity()
// return@getPlateData
// }
//// if (tempUserId != currentUserId) {
// SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0)
//// } else {
//// SoundPoolUtil.getInstance().play(COLLECT_SUCCESS, 0)
//// }
// loadLogInfo("collectFace,openPlate: item3:${item}")
// item.faceId = userId
// item.updateTime = DateTimeUtils.getDateTimeString()
// item.plateNumber = null
// plateEnable = false
// Debouncer(5000).debounce {
// //打开柜子
// PlateUtils.open(this, item.equipmentBoxCode, false)
// }
// currentPlateInfo = item
// MyApp.plateList = items
// SpTool.savePlateData(items)
// }
// }
//
//// private val debouncer by lazy { Debouncer(3000) }
//
// private fun initArcView() {
// //在布局结束后才做初始化操作
// binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
// recognizeViewModel.getCompareResultList().getValue()
// }
//
// override fun onDestroy() {
// loadLogInfo("onDestroy")
// instance = null
// if (rgbCameraHelper != null) {
// rgbCameraHelper!!.release()
// rgbCameraHelper = null
// }
//
// recognizeViewModel.destroy()
//
// EventBus.getDefault().unregister(this)
// super.onDestroy()
// }
//
//
// /**
// * 调整View的宽高,使预览显示正常且采集框固定为380x380
// *
// * @param rgbPreview RGB预览View
// * @param previewView 显示预览数据的view
// * @param faceRectView 画框的view (380x380)
// * @param previewSize 预览大小
// * @param displayOrientation 相机旋转角度
// * @param scale 缩放比例
// * @return 调整后的LayoutParams
// */
// private fun adjustPreviewViewSize(
// rgbPreview: View,
// previewView: View,
// faceRectView: FaceRectView,
// previewSize: Camera.Size,
// displayOrientation: Int,
// scale: Float
// ): ViewGroup.LayoutParams {
// val layoutParams = previewView.layoutParams
// val measuredWidth = previewView.measuredWidth
// val measuredHeight = previewView.measuredHeight
// var ratio = (previewSize.height.toFloat()) / previewSize.width.toFloat()
// if (ratio > 1) {
// ratio = 1 / ratio
// }
// if (displayOrientation % 180 == 0) {
// layoutParams.width = measuredWidth
// layoutParams.height = (measuredWidth * ratio).toInt()
// } else {
// layoutParams.height = measuredHeight
// layoutParams.width = (measuredHeight * ratio).toInt()
// }
// if (scale < 1f) {
// val rgbParam = rgbPreview.getLayoutParams()
// layoutParams.width = (rgbParam.width * scale).toInt()
// layoutParams.height = (rgbParam.height * scale).toInt()
// } else {
// layoutParams.width = (layoutParams.width * scale).toInt()
// layoutParams.height = (layoutParams.height * scale).toInt()
// }
//
// val metrics = DisplayMetrics()
// windowManager.defaultDisplay.getMetrics(metrics)
//
// if (layoutParams.width >= metrics.widthPixels) {
// val viewRatio = layoutParams.width / (metrics.widthPixels.toFloat())
// layoutParams.width = (layoutParams.width / viewRatio).toInt()
// layoutParams.height = (layoutParams.height / viewRatio).toInt()
// }
// if (layoutParams.height >= metrics.heightPixels) {
// val viewRatio = layoutParams.height / (metrics.heightPixels.toFloat())
// layoutParams.width = (layoutParams.width / viewRatio).toInt()
// layoutParams.height = (layoutParams.height / viewRatio).toInt()
// }
//
// previewView.setLayoutParams(layoutParams)
// faceRectView.setLayoutParams(layoutParams)
// return layoutParams
// }
//
// private fun initRgbCamera() {
// val cameraListener: CameraListener = object : CameraListener {
// override fun onCameraOpened(
// camera: Camera,
// cameraId: Int,
// displayOrientation: Int,
// isMirror: Boolean
// ) {
// loadLogInfo("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
// runOnUiThread({
// val previewSizeRgb = camera.getParameters().getPreviewSize()
// val layoutParams = adjustPreviewViewSize(
// binding.dualCameraTexturePreviewRgb,
// binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView,
// previewSizeRgb, displayOrientation, 0.6f
// )
// loadLogInfo("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
// loadLogInfo("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
// loadLogInfo(
// "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${
// ConfigUtil.isDrawRgbRectHorizontalMirror(
// context
// )
// }, isDrawRgbRectVerticalMirror = ${
// ConfigUtil.isDrawRgbRectVerticalMirror(
// context
// )
// }"
// )
// rgbFaceRectTransformer = FaceRectTransformer(
// previewSizeRgb.width,
// previewSizeRgb.height,
// layoutParams.width,
// layoutParams.height,
// 90,
// cameraId,
// isMirror,
// true,
// true
// )
//
// recognizeViewModel.onRgbCameraOpened(camera)
// recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer)
// })
// }
//
// @RequiresApi(api = Build.VERSION_CODES.Q)
// override fun onPreview(nv21: ByteArray?, camera: Camera?) {
// if (!isRecognition) {
// return
// }
// binding.dualCameraFaceRectView.clearFaceInfo()
// facePreviewInfoList = recognizeViewModel.onPreviewFrame(nv21, true)
// if (facePreviewInfoList != null && rgbFaceRectTransformer != null) {
// drawPreviewInfo(facePreviewInfoList)
// }
// if (facePreviewInfoList.isNullOrEmpty()) {
// currentPlateInfo = null
// currentUserId = null
// userFaceInfo = null
// }
// recognizeViewModel.clearLeftFace(facePreviewInfoList)
// }
//
// override fun onCameraClosed() {
// Timber.i("initRgbCamera onCameraClosed: ")
// }
//
// override fun onCameraError(e: java.lang.Exception) {
// Timber.i("initRgbCamera onCameraError: %s", e.message)
// e.printStackTrace()
// }
//
// override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
// Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}")
// if (rgbFaceRectTransformer != null) {
// rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
// }
// Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation")
// }
// }
//
// val previewConfig: PreviewConfig = recognizeViewModel.previewConfig
// rgbCameraHelper = DualCameraHelper.Builder()
// .previewViewSize(
// Point(
// binding.dualCameraTexturePreviewRgb.measuredWidth,
// binding.dualCameraTexturePreviewRgb.measuredHeight
// )
// )
// .rotation(windowManager.defaultDisplay.rotation)
// .additionalRotation(0) // 角度
// .previewSize(recognizeViewModel.loadPreviewSize())
// .specificCameraId(previewConfig.rgbCameraId)
// .isMirror(true)
// .previewOn(binding.dualCameraTexturePreviewRgb)
// .cameraListener(cameraListener)
// .build()
// rgbCameraHelper!!.init()
// rgbCameraHelper!!.start()
// }
//
// /**
// * 绘制RGB、IR画面的实时人脸信息
// *
// * @param facePreviewInfoList RGB画面的实时人脸信息
// */
// private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo>?) {
// if (rgbFaceRectTransformer != null) {
// val rgbDrawInfoList: MutableList<DrawInfo>? = 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()
// registerHandler = RegisterCallbackHandler()
// try {
// if (isFirstOpen) {
// goInitActivity()
// binding.root.postDelayed({
// isFirstOpen = false
// }, 1000)
// return
// }
// retryFailCount = 0
// binding.tvSimilarValue.text = ""
// isCollecting.set(false)
// isCollectFace = false
// plateEnable = true
// collectCount = 0
// tempUserId = "${System.currentTimeMillis()}"
// resumeCamera()
// userViewModel.resetUserInfo()
// countDownTimer?.let {
// it.cancel()
// it.start()
// }
//// initFaceScheduler()
//// checkFaceState()
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
//
// private var isFirstOpen = true
//
// protected override fun onPause() {
// pauseCamera()
// super.onPause()
// countDownTimer?.cancel()
// }
//
// private fun pauseCamera() {
// isRecognition = false
// recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
// }
//
// override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
// super.handleLoginSuccess(equipmentUserInfo, isAdmin)
//// goInitActivity()
// }
//
// private val totalTimeInMillis = 15 * 1000L
// fun initCountTime() {
// countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
// override fun onTick(millisUntilFinished: Long) {
// loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}")
// binding.tvToInit.text = "${(millisUntilFinished / 1000).toInt() + 1}"
// }
//
// override fun onFinish() {
// goInitActivity()
// }
// }
// }
//
// fun goInitActivity() {
// val intent = Intent(context, InitActivity::class.java)
// startActivity(intent)
// }
//
// /**
// * 采集人脸
// */
// private fun collectFace() {
// loadLogInfo("collectFace: -----------1,开始采集人脸,开始时间:${DateTimeUtils.getDateTimeString()}----------")
// if (isFirstOpen) return
// loadLogInfo("collectFace: ----------2,isFirstOpen=false-----------")
// loadLogInfo("collectFace: ----------4,检测到人脸信息-----------")
// currentUserId = null
// userFaceInfo = null
// recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
// loadLogInfo("collectFace: ----------5,人脸采集中-----------")
// }
//
// private val uploadFaceDebouncer = Debouncer(10000)
//
// /**
// * 改为上传人脸数据,不需要发生人脸数据到收银端
// */
// private fun sendFaceData(faceData: String, headBmp: Bitmap) {
// loadLogInfo("collectFace,sendFaceData: ----------11,发送人脸数据-----------")
//
// val imageFile = BitmapSaver.saveToAppFilesDir(
// headBmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
// )
// loadLogInfo("collectFace,sendFaceData:${imageFile?.absolutePath}")
// if (imageFile == null) {
// ToastUtils.showToast("获取人脸照片失败")
// return
// }
// uploadFaceDebouncer.debounce {
// addUserFace(faceData, imageFile)
// }
//// // 给某个客户端发送
//// val jsonObject = JSONObject().also {
//// it.put("type", "faceFeature")
//// it.put("content", faceData)
//// }
//// tcpClient?.send(jsonObject)
// }
//
// private fun addUserFace(faceData: String, imageFile: File) {
// settingViewModel.addUserFace(faceData, imageFile) { status, faceModel ->
// loadLogInfo("collectFace,saveFaceInfo: ----------12,人脸数据发送完成时间:${DateTimeUtils.getDateTimeString()},结果:$status-----------")
// if (status.not()) {
// ToastUtils.showToast("新增数据失败")
// loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据失败,faceData=${faceData}")
// return@addUserFace
// }
// if (faceModel == null) {
// ToastUtils.showToast("新增数据成功,但未拿到接口数据")
// loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据成功但接口返回为nullfaceData=${faceData}")
// return@addUserFace
// }
// Thread {
// saveFaceInfo(faceModel)
// uploadFaceDebouncer.reset()
// }.start()
// }
// }
//
// private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) {
// if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) {
// return
// }
// val faceData: String? = Base64.encode(userFaceInfo?.faceFeature?.featureData)
// if (faceData.isNullOrBlank()) {
// 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)
// }
//
// private var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
// private var userFaceInfo: UserFaceInfo? = null
//
// private var currentUserId: String? = null
//
// private fun saveFaceInfo(faceModel: UserFaceModel) {
// try {
// loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------")
// val faceDao = FaceDatabase.getInstance(this).faceDao()
// val faceEntity = FaceEntity(
// faceModel.userId,
// null,
// Base64.decode(faceModel.faceFeatureStr)
// ).also {
// //1-会员、2-临时用户
// it.userType = if (faceModel.isMember) "1" else "2"
// }
// faceDao.insert(faceEntity)
// loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------")
// recognizeViewModel.refreshFaceList()
// loadLogInfo("collectFace,saveFaceInfo: ----------11,刷新人脸数据-----------")
// runOnUiThread { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) }
// loadLogInfo("---------------------------------")
// collectCount = 0
// } catch (e: Exception) {
// e.printStackTrace()
// loadLogInfo("collectFace,saveFaceInfo: ----------13,人脸采集异常:${e.message}-----------")
// collectCount++
// loadLogInfo("collectFace,saveFaceInfo: ----------14,${collectCount}次人脸采集异常:${e.message}-----------")
// ToastUtils.showToast("人脸采集异常:${e.message}")
// }
// }
//
// private var collectCount = 0
//
//// private var tcpClient: TcpClient? = null
////
//// private fun addSocketListener() {
////// val ipAddress = NetworkUtils.getIPAddress(true)
////// loadLogInfo( "addSocketListener,ipAddress: $ipAddress");
//// tcpClient = TcpClient(
//// "192.168.1.95",
//// 5000,
//// GlobalData.deviceId, // clientId
//// 5000, // connectTimeoutMs
//// 10 * 1000, // heartbeatIntervalMs
//// 30 * 1000 // heartbeatTimeoutMs
//// )
////
//// tcpClient?.setListener(object : TcpClientListenerImpl() {
//// override fun onSendSuccess(json: JSONObject?) {
//// super.onSendSuccess(json)
//// loadLogInfo("collectFace,TcpClientListener发送成功:$json")
////// toast("发送成功")
//// }
////
//// override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) {
//// super.onSendFailed(json, e)
//// ToastUtils.showToast("发送失败${e?.message}")
//// loadLogInfo("collectFace,TcpClientListener人脸数据发送失败:${e?.message}")
//// }
////
//// override fun onMessage(json: JSONObject?) {
//// super.onMessage(json)
//// loadLogInfo("collectFace,TcpClientListener收到消息:$json")
//// val type = json?.getInt("type")
//// // 根据服务端消息类型处理删除新增临时用户人脸数据---------------------
//// when (type) {
//// LanServer.TYPE_ADD_FACE_DATA -> {
//// //采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据
//// loadLogInfo("collectFace,TcpClientListener收到消息,新增用户:采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据")
//// }
////
//// LanServer.TYPE_CLEAR_FACE_DATA -> {
//// clearFaceData()
//// }
////
//// else -> {}
//// }
//// }
//// })
//// tcpClient?.start()
////
//// try {
//// LanServer.getInstance().let {
//// it.setListener(object : LanServerListenerImpl() {
//// override fun onMessageReceived(clientId: String?, message: JSONObject?) {
//// super.onMessageReceived(clientId, message)
//// ToastUtils.showToast("收到消息clientId=$clientId")
//// }
//// })
//// it.start()
//// }
//// } catch (e: Exception) {
//// e.printStackTrace()
//// }
//// }
//
// private fun clearFaceData() {
// Thread {
// val faceDao = FaceDatabase.getInstance(this).faceDao()
// faceDao.deleteTempUserFaceData()
// recognizeViewModel.refreshFaceList()
// loadLogInfo("collectFace,TcpClientListener收到消息,已删除并刷新人脸数据")
// }.start()
// }
//
// private var isCollectFace = false
//
//}
@@ -86,7 +86,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onRightDoubleClick() {
if (pageType == PageType.SETTING_LIST) {
loadClearDialog()
startActivity(Intent(this, FunActivity::class.java))
}
}
@@ -104,25 +104,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
super.registerDataChange()
}
/**
* 加载清除数据弹窗
*/
private fun loadClearDialog() {
CustomDialog(
activity = this,
content = "请确认是否清除本地所有数据,并重新拉取全量人脸数据?",
onConfirm = {
showWaitingDialog("加载中……")
clearAllFace {
userViewModel.getUserFaceCache { status, msg ->
hideWaitingDialog()
ToastUtils.showToast(
if (status) "请求成功,$msg" else "请求失败,$msg"
)
}
}
}).show()
}
}
/**
@@ -69,4 +69,36 @@ fun View.clickWithCoroutines(
lastClickTime = currentTime
}
}
}
//fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
// var job: Job? = null
// setOnClickListener {
// job?.cancel()
// job = CoroutineScope(Dispatchers.Main).launch {
// delay(delay)
// action()
// }
// }
//}
/**
* 极简版防重复点击扩展函数
* @param delay 防抖时间(默认300ms
* @param action 点击执行逻辑
*/
fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
setOnClickListener {
// 用View的tag存储是否可点击的状态(默认可点击)
if (tag as? Boolean ?: true) {
tag = false // 标记为不可点击
action() // 立即执行点击逻辑
// 启动协程,延迟后恢复可点击状态
CoroutineScope(Dispatchers.Main).launch {
delay(delay)
tag = true // 恢复可点击
}
}
}
}
@@ -2,7 +2,6 @@ package com.sw.platecabinet.model.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
/**
@@ -38,5 +37,5 @@ data class UserFaceModel(
/**
* 是否会员
*/
val isMember: Boolean
val member: Boolean
) : Parcelable
@@ -12,37 +12,29 @@ import timber.log.Timber
import java.util.concurrent.TimeUnit
object ApiClient {
//private const val BASE_URL = "https://vip.shuziweidao.com"
// private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889/"
private var DEVICE_BASE_URL = GlobalData.appBaseUrl
// private const val BASE_URL = "http://192.168.1.8:9092"
// private const val TIME_OUT = 30L // 超时时间(秒)
private const val TIME_OUT = 60L // 超时时间(秒)
val okHttpClient = OkHttpClient.Builder()
private const val TIME_OUT = 60L // 超时时间(秒)
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Timber.d("okhttp logger ==>${it}")
}).apply {
level = if (MyApp.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
.addNetworkInterceptor(HttpLoggingInterceptor { Timber.d("okhttp ==>${it}") }.apply {
level = if (MyApp.DEBUG) HttpLoggingInterceptor.Level.BODY
else HttpLoggingInterceptor.Level.NONE
})
.addInterceptor(RequestInterceptor())
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(DEVICE_BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
// .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器
.build()
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(GlobalData.appBaseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val apiService: ApiService by lazy {
retrofit.create(ApiService::class.java)
}
}
}
@@ -0,0 +1,79 @@
package com.sw.platecabinet.utils
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.util.concurrent.atomic.AtomicBoolean
/**
* 基于协程的倒计时工具类(实例化使用,各调用方持有独立实例,互不干扰)
* 修复:onCompletion 区分正常结束与取消,手动取消时不触发 onFinish
*/
class CountDownUtil {
private val isCounting = AtomicBoolean(false)
private var currentJob: Job? = null
/**
* 启动倒计时(若已在运行则忽略,需重启请先调用 cancelCountDown
* @param total 总秒数
* @param lifecycleScope Activity/Fragment 的 lifecycleScope
* @param onStart 启动回调(主线程)
* @param onTick 每秒回调剩余秒数(主线程)
* @param onFinish 正常结束回调(主线程,手动取消时不触发)
*/
fun startCountDown(
total: Int = 15,
lifecycleScope: CoroutineScope,
onStart: () -> Unit = {},
onTick: (Int) -> Unit = {},
onFinish: () -> Unit
) {
if (isCounting.get()) return
isCounting.set(true)
currentJob = flow {
for (i in total downTo 1) {
emit(i)
delay(1000L)
}
}
.flowOn(Dispatchers.IO)
.onStart {
withContext(Dispatchers.Main) { onStart() }
}
.onEach { remaining ->
withContext(Dispatchers.Main) { onTick(remaining) }
}
.onCompletion { cause ->
withContext(Dispatchers.Main) {
isCounting.set(false)
currentJob = null
// cause == null 为正常结束,有异常(含 CancellationException)时不触发 onFinish
if (cause == null) {
Log.d("CountDownUtil", "倒计时正常结束")
onFinish()
} else {
Log.d("CountDownUtil", "倒计时被取消:${cause.message}")
}
}
}
.catch { e ->
Log.e("CountDownUtil", "倒计时异常:${e.message}", e)
isCounting.set(false)
currentJob = null
}
.launchIn(lifecycleScope)
}
/**
* 取消倒计时(不触发 onFinish)
*/
fun cancelCountDown() {
currentJob?.cancel()
currentJob = null
isCounting.set(false)
Log.d("CountDownUtil", "倒计时已取消")
}
}
@@ -15,7 +15,8 @@ object PlateUtils {
fragmentActivity: FragmentActivity,
code: String?,
isScanCode: Boolean,
isSetting: Boolean = false
isSetting: Boolean = false,
callback: () -> Unit = {}
) {
val activity = fragmentActivity as BaseActivity<*>
Timber.d("itemClick llOpen equipmentBoxCode = ${code}")
@@ -36,15 +37,30 @@ object PlateUtils {
)
} else {
ToastUtils.showToast("柜门打开成功")
callback()
}
}
override fun onFail(e: Exception?) {
activity.hideWaitingDialog()
ToastUtils.showToast("柜门打开失败")
callback()
}
})
}
fun openDirect(boxCode: String?) {
val boxNumber = boxCode?.toIntOrNull() ?: -1
if (boxNumber < 0) return
SerialApi.openPlate(boxNumber, object : SerialPortManager.SendCallback {
override fun onSuccess() {
}
override fun onFail(e: Exception?) {
}
})
}
}
@@ -6,10 +6,14 @@ import com.sw.inbound.utils.SPUtil
import com.sw.inbound.utils.SPUtil.Companion.getInstance
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.model.response.EquipmentUserInfo
import kotlin.text.get
object SpTool {
const val LAST_FACE_TIMESTAMP: String = "faceTimestamp"
const val PLATE_BOX_DATA: String = "plateBoxBind"
const val IS_FIRST_GET_FACE = "isFirstGetFace"
private var spUtil: SPUtil? = null
init {
@@ -39,4 +43,10 @@ object SpTool {
return GsonUtils.fromJsonList(jsonData, EquipmentUserInfo::class.java)
}
var firstGetFace: Boolean
get() = spUtil?.get(IS_FIRST_GET_FACE, true)?:true
set(value) {
spUtil?.boolean(IS_FIRST_GET_FACE, value)
}
}
@@ -9,16 +9,12 @@ import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.FaceApi
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalData.globalEquipmentCode
import com.sw.platecabinet.GlobalKey
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -55,6 +51,19 @@ class UserViewModel : BaseViewModel() {
// }
// }
fun getUserFace(
pageNo: Int,
pageSize: Int,
callback: (List<UserFaceModel>) -> Unit
) {
launchWithLoading{
val response = repository.getUserFaceCache(pageNum = pageNo, pageSize = pageSize)
val status = parseResponse(response)
if (status) callback(response.data?:emptyList())
else emptyList<UserFaceModel>()
}
}
/**
* 获取人脸数据
*/
@@ -79,9 +88,15 @@ class UserViewModel : BaseViewModel() {
return@launchWithLoading
}
val faceEntityList = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).apply {
userType = if (it.member) "1" else "2"
}
}
withContext(Dispatchers.Default) {
if (currentPageNo == 1) {
//第一页数据删除本地数据库
faceApi.clearFaceData()
}
faceApi.updateFaceData(pageNo, faceEntityList)
if (list.size >= pageSize) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
@@ -103,6 +118,7 @@ class UserViewModel : BaseViewModel() {
private var faceTimestamp = 0L
/**
* 激活人脸识别引擎
*/
+110
View File
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools">
<include
android:id="@+id/includeHeader"
layout="@layout/item_title_time" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_vertical"
android:layout_marginTop="30dp">
<TextView
android:id="@+id/tvMemberUserCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textSize="16sp"
android:textColor="@color/white"
android:gravity="center"
tools:text="会员用户:100人"/>
<View
android:layout_width="1dp"
android:layout_height="30dp"
android:background="@color/white"/>
<TextView
android:id="@+id/tvTempUserCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textSize="16sp"
android:textColor="@color/white"
android:gravity="center"
tools:text="临时用户:100人"/>
</LinearLayout>
<TextView
android:id="@+id/btnClearFace"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="清除人脸数据"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnClearAndGetAll"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="清除并拉取全量数据"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnOpenAllPlate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="打开全部柜子"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnPutPlate"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="模拟放置餐盘"
android:textColor="#FFCC99" />
<TextView
android:id="@+id/btnTestFeature"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:textSize="24sp"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:paddingHorizontal="30dp"
android:background="@drawable/btn_outline"
android:text="人脸特征比较"
android:textColor="#FFCC99"
android:visibility="gone"/>
</LinearLayout>
@@ -105,6 +105,46 @@
android:textSize="36sp"
tools:text="30" />
<LinearLayout
android:id="@+id/llCollectButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_gravity="bottom"
android:layout_marginBottom="100dp"
android:visibility="gone">
<TextView
android:id="@+id/btnRetry"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="再次重试"
android:textColor="#FFCC99"
android:textSize="24sp"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="10dp"
android:paddingHorizontal="45dp"
android:gravity="center"
android:background="@drawable/btn_outline"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/btnCollect"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="新用户采集"
android:textColor="#FFCC99"
android:textSize="24sp"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="10dp"
android:paddingHorizontal="45dp"
android:gravity="center"
android:background="@drawable/btn_outline"
tools:ignore="HardcodedText" />
</LinearLayout>
<!-- <LinearLayout-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
@@ -173,6 +213,14 @@
<!-- android:src="@drawable/ic_log_show"-->
<!-- android:textSize="16sp" />-->
<ImageView
android:id="@+id/ivCollectPhoto"
android:layout_width="200dp"
android:layout_height="200dp"
android:adjustViewBounds="true"
android:layout_gravity="top|start"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
+1 -1
View File
@@ -62,6 +62,6 @@ dependencies {
implementation("com.google.code.gson:gson:2.8.6")
val glideVersion = "4.12.0"
implementation("com.github.bumptech.glide:glide:$glideVersion")
api("com.github.bumptech.glide:glide:$glideVersion")
annotationProcessor("com.github.bumptech.glide:compiler:$glideVersion")
}
@@ -40,6 +40,12 @@ public class FaceApi {
faceDao.insert(list);
}
public void clearFaceData() {
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
faceDao.deleteAll();
faceDao.resetId();
}
/**
* 激活arcsoft 人脸
*
@@ -46,6 +46,10 @@ public class FaceRectView extends View {
}
}
public int getRectColor() {
return paint.getColor();
}
public void clearFaceInfo() {
drawInfoList.clear();
postInvalidate();
@@ -92,9 +92,25 @@ public interface FaceDao {
@Query("SELECT * FROM face WHERE user_name = :userName limit 1")
FaceEntity queryByUserName(String userName);
/**
* 查询指定用户的所有人脸记录(同一用户可能存有多条特征数据)
*
* @param userName 用户ID
* @return 该用户所有人脸记录列表
*/
@Query("SELECT * FROM face WHERE user_name = :userName")
List<FaceEntity> queryAllByUserName(String userName);
/**
* @return 删除临时用户人脸
*/
@Query("DELETE from face WHERE user_type = '2'")
int deleteTempUserFaceData();
/**
* 查询用户人脸数
* @param userType 1-会员,2-临时用户
*/
@Query("SELECT COUNT(1) FROM face WHERE user_type = :userType")
int getFaceCount(String userType);
}