FacialLoginActivity.kt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. package xn.hxp.ui.login
  2. import android.os.Bundle
  3. import android.os.Handler
  4. import android.os.Looper
  5. import android.util.Log
  6. import android.view.LayoutInflater
  7. import android.view.MotionEvent
  8. import android.view.View
  9. import android.widget.TextView
  10. import androidx.viewbinding.ViewBinding
  11. import com.blankj.utilcode.util.LogUtils
  12. import com.bumptech.glide.Glide
  13. import com.bumptech.glide.load.engine.DiskCacheStrategy
  14. import com.bumptech.glide.request.RequestOptions
  15. import com.rc.core.ui.activity.BaseActivity
  16. import com.rc.httpcore.HttpClient
  17. import com.rc.httpcore.HttpConfig
  18. import com.rc.httpcore.client.ApiRepository
  19. import com.rc.httpcore.exception.NetException
  20. import com.rc.httpcore.vo.request.FaceCompareReq
  21. import io.fotoapparat.Fotoapparat
  22. import io.fotoapparat.facedetector.processor.FaceDetectorProcessor
  23. import io.fotoapparat.log.fileLogger
  24. import io.fotoapparat.log.logcat
  25. import io.fotoapparat.log.loggers
  26. import io.fotoapparat.selector.back
  27. import io.fotoapparat.selector.front
  28. import retrofit2.HttpException
  29. import xn.hxp.R
  30. import xn.hxp.app.ChemicalApp
  31. import xn.hxp.comm.Constants
  32. import xn.hxp.databinding.ActivityFacialLoginBinding
  33. import xn.hxp.utils.AudioPlayer
  34. import xn.hxp.utils.UiManager
  35. import xn.hxp.weidith.AuthenticationDialog
  36. import xn.hxp.weidith.CustomDialog
  37. import java.io.File
  38. import java.lang.Boolean
  39. import java.net.ConnectException
  40. import java.net.SocketTimeoutException
  41. import java.text.SimpleDateFormat
  42. import java.util.Calendar
  43. import java.util.Locale
  44. /**
  45. * 人脸登录
  46. */
  47. class FacialLoginActivity : BaseActivity() {
  48. private lateinit var mFotoapparat: Fotoapparat
  49. // private lateinit var mCountDownTimer: CountDownTimer
  50. private lateinit var faceList: String
  51. private val handlerBack = Handler(Looper.getMainLooper())
  52. private var timeLeftInSeconds = 2
  53. private var mTvView: TextView? = null
  54. private var mDialogsAut: AuthenticationDialog? = null
  55. lateinit var viewBinding: ActivityFacialLoginBinding
  56. override fun setViewBinding(): ViewBinding {
  57. viewBinding = ActivityFacialLoginBinding.inflate(layoutInflater)
  58. return viewBinding
  59. }
  60. override fun onInit() {
  61. AudioPlayer.getInstance().play(R.raw.face_detect_hint)
  62. // 使用 Glide 加载网络图片
  63. viewBinding.deptName.text = "${ChemicalApp.confs!!.deptName}-${ChemicalApp.confs!!.roomNum}"
  64. Glide.with(this)
  65. .load("${HttpConfig.API_BASE_IMG_URL}${ChemicalApp.confs!!.circularLogo}")
  66. .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC))
  67. .into(viewBinding.image)
  68. var str = ""
  69. try {
  70. str = intent.getStringExtra("faceList")!!
  71. } catch (e: Exception) {
  72. }
  73. val mtypes = intent.getStringExtra("mtypes")
  74. try {
  75. when (mtypes) {
  76. "1" -> {
  77. viewBinding.linType.visibility = View.GONE
  78. }
  79. "4" -> {
  80. viewBinding.tvScan.visibility = View.GONE
  81. }
  82. "5" -> {
  83. viewBinding.tvSwipe.visibility = View.GONE
  84. }
  85. }
  86. } catch (e: Exception) {
  87. }
  88. val map = mutableMapOf<String, String>()
  89. if (mtypes != null) {
  90. map["mtypes"] = mtypes
  91. }
  92. if (str != null && str.length > 0) {
  93. // 去除字符串首尾的中括号
  94. faceList = str.removeSurrounding("[", "]")
  95. map["faceList"] = str
  96. }
  97. //跳转刷卡登录
  98. viewBinding.tvSwipe.setOnClickListener {
  99. UiManager.switcher(this, map, SwipeActivity::class.java)
  100. finish()
  101. }
  102. //跳转扫码登录
  103. viewBinding.tvScan.setOnClickListener {
  104. UiManager.switcher(this, map, ScanLoginActivity::class.java)
  105. finish()
  106. }
  107. viewBinding.tvReturn.text = "返回${ChemicalApp.confs!!.backTime}s"
  108. viewBinding.tvReturn.setOnClickListener {
  109. finish()
  110. }
  111. mFotoapparat = Fotoapparat.with(this)
  112. .into(viewBinding.cameraView)
  113. .lensPosition(
  114. if (Constants.FACE_TAG == 0) {
  115. front()
  116. } else {
  117. back()
  118. }
  119. )//front()前置 back 后置
  120. .frameProcessor(
  121. FaceDetectorProcessor.with(this)
  122. .listener { faces ->
  123. viewBinding.rectanglesView.setRectangles(faces)
  124. }.build()
  125. )
  126. .logger(loggers(logcat(), fileLogger(this)))
  127. .build()
  128. mFotoapparat.stop()
  129. mFotoapparat.start()
  130. handlerBack.post(countdownRunnableTwo)
  131. }
  132. override fun onBackPressed() {
  133. super.onBackPressed()
  134. finish()
  135. }
  136. override fun cdTime(cd: Int) {
  137. viewBinding.tvReturn.text = "返回${cd}s"
  138. }
  139. override fun onDestroy() {
  140. super.onDestroy()
  141. if (null != mDialogsAut && mDialogsAut!!.isShowing) {
  142. mDialogsAut!!.dismiss()
  143. }
  144. // cancelTime()//手动关闭
  145. LogUtils.i("=============onDestroy")
  146. // handlerUtil.removeCallbacks(task)
  147. // handlerUtil.stopAllTasks()
  148. // 移除回调,以防止内存泄漏
  149. try {
  150. mFotoapparat.stop()
  151. handlerBack.removeCallbacks(countdownRunnable)
  152. handlerBack.removeCallbacks(countdownRunnableTwo)
  153. handlerBack.removeCallbacksAndMessages(null)
  154. dismissLoading()
  155. } catch (e: Exception) {
  156. }
  157. }
  158. override fun cdFinish() {
  159. finish()
  160. }
  161. private fun takePicture() {
  162. LogUtils.i("=======111人脸登录任务进行中")
  163. val format = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault())
  164. val fileName = "${format.format(Calendar.getInstance().time)}.jpg"
  165. val photoFile = File(this.getExternalFilesDir("photos"), fileName)
  166. try {
  167. mFotoapparat.takePicture()
  168. .saveToFile(photoFile)
  169. .whenAvailable {
  170. callFaceMatchingApi(photoFile)
  171. }
  172. } catch (e: Exception) {
  173. }
  174. }
  175. private fun callFaceMatchingApi(
  176. featureData: File,
  177. ) {
  178. showLoading("比对中....")
  179. val param = FaceCompareReq().apply {
  180. data = null
  181. userIds = faceList
  182. }
  183. val disposable = ApiRepository.faceCompare(featureData, param)
  184. .subscribe({ success ->
  185. dismissLoading()
  186. ChemicalApp.userData = success
  187. LogUtils.d(success.userId, ChemicalApp.subjectId)
  188. authenticationInfo(success.userId, ChemicalApp.subjectId!!)
  189. }, { throwable ->
  190. dismissLoading()
  191. throwableView(throwable)
  192. })
  193. addDisposable(disposable)
  194. }
  195. //验证当前人员身份
  196. private fun authenticationInfo(userId: String, subId: String) {
  197. LogUtils.d(userId, subId)
  198. showLoading("验证中...")
  199. val disposable = ApiRepository.userCardValidation(userId, subId)
  200. .subscribe({ data ->
  201. dismissLoading()
  202. val allFalse =
  203. Boolean.TRUE == data.cabinetAdmin || Boolean.TRUE == data.belongUser || Boolean.TRUE == data.toipcUser || Boolean.TRUE == data.safeUser || Boolean.TRUE == data.collegeAdmin || Boolean.TRUE == data.schoolLevelAdmin || Boolean.TRUE == data.adminUser || Boolean.TRUE == data.apply || Boolean.TRUE == data.white
  204. if (!allFalse) {
  205. HttpClient.token = null
  206. ChemicalApp.userData = null
  207. customDialogView(2, "身份认证不通过")
  208. } else {
  209. //校级管理员 schoolLevelAdmin
  210. //院级管理员 collegeAdmin
  211. //实验室负责人 adminUser
  212. //安全负责人 safeUser
  213. //柜锁管理员 cabinetAdmin
  214. //是否化学品归属人 belongUser
  215. //是否化学品归属课题组下成员 toipcUser
  216. if (data.schoolLevelAdmin == true || data.collegeAdmin == true) {
  217. //院级管理员 or 校级管理员
  218. authenticationDialog(data.faceImg, data.userName)
  219. } else if (data.adminUser == true || data.safeUser == true || data.cabinetAdmin == true) {
  220. //实验室负责人 or 安全负责人 or 柜锁管理员
  221. authenticationDialog(data.faceImg, data.userName)
  222. } else if (data.belongUser == true || data.toipcUser == true) { //当前身份 归属人or课题组
  223. authenticationDialog(data.faceImg, data.userName)
  224. } else if (data.white == true || data.apply == true) {// 白名单和实验室准入
  225. authenticationDialog(data.faceImg, data.userName)
  226. } else {
  227. HttpClient.token = null
  228. ChemicalApp.userData = null
  229. customDialogView(2, "身份认证不通过")
  230. }
  231. }
  232. }, { throwable ->
  233. dismissLoading()
  234. //暂时注释掉 防止异步请求后 在认证时 token 丢失
  235. // startCountdownAndExecuteMethod()
  236. throwableView(throwable)
  237. })
  238. addDisposable(disposable)
  239. }
  240. //身份认证成功
  241. private fun authenticationDialog(faceImg: String?, userName: String) {
  242. mFotoapparat.stop()
  243. handlerBack.removeCallbacks(countdownRunnableTwo)
  244. AudioPlayer.getInstance().play(R.raw.verify_success)
  245. mDialogsAut = AuthenticationDialog(
  246. this,
  247. faceImg,
  248. ChemicalApp.confs!!.subName,
  249. ChemicalApp.confs!!.deptName,
  250. "${ChemicalApp.confs!!.buildName}${ChemicalApp.confs!!.floorName}",
  251. userName, object : AuthenticationDialog.IClickLit {
  252. override fun onUpView(tvView: TextView) {
  253. mTvView = tvView
  254. }
  255. })
  256. mDialogsAut!!.show()
  257. // 开始倒计时
  258. handlerBack.post(countdownRunnable)
  259. // 获取对话框的 Window 对象
  260. mDialogsAut!!.window?.decorView?.setOnTouchListener { _, event ->
  261. // 判断是否点击了对话框外部空白区域
  262. if (event.action == MotionEvent.ACTION_DOWN) {
  263. val x = event.x
  264. val y = event.y
  265. val dialogView = mDialogsAut!!.window?.decorView
  266. if (dialogView != null && (x < 0 || x > dialogView.width || y < 0 || y > dialogView.height)) {
  267. // 在此处执行点击对话框外部空白区域时的操作
  268. // 例如关闭对话框
  269. // 移除回调,以防止内存泄漏
  270. mDialogsAut!!.dismiss()
  271. finish()
  272. return@setOnTouchListener true
  273. }
  274. }
  275. return@setOnTouchListener false
  276. }
  277. }
  278. private val countdownRunnable = object : Runnable {
  279. override fun run() {
  280. if (timeLeftInSeconds > 0) {
  281. mTvView!!.text = "${timeLeftInSeconds}秒后自动返回首页"
  282. timeLeftInSeconds--
  283. handlerBack.postDelayed(this, 1000)
  284. } else {
  285. mDialogsAut!!.dismiss()
  286. finish()
  287. }
  288. }
  289. }
  290. private val countdownRunnableTwo = object : Runnable {
  291. override fun run() {
  292. takePicture()
  293. handlerBack.postDelayed(this, 4000)
  294. }
  295. }
  296. /**
  297. * 0 没有图标 1 绿色(成功) 2红色(失败)
  298. * 失败或者成功的弹框
  299. */
  300. private fun customDialogView(types: Int, msg: String) {
  301. val customDialog = CustomDialog(this, types, msg)
  302. if (!this.isFinishing && !this.isDestroyed) {
  303. customDialog.show()
  304. }
  305. }
  306. /**
  307. * 异常处理
  308. */
  309. private fun throwableView(throwable: Throwable) {
  310. LogUtils.e(Log.getStackTraceString(throwable))
  311. when (throwable) {
  312. is NetException -> {
  313. if (throwable.message.isNullOrEmpty()) {
  314. "接口请求失败(${throwable.code})"
  315. } else {
  316. throwable.message!!
  317. }
  318. }
  319. is SocketTimeoutException -> "请求超时,请稍后重试"
  320. is ConnectException -> "无法连接服务器,请检查网络"
  321. is HttpException -> "服务器繁忙,请稍后重试"
  322. else -> "服务器异常"
  323. }?.let { customDialogView(2, "$it") }
  324. }
  325. }