1 /*
<lambda>null2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.settings.biometrics2.ui.view
17
18 import android.annotation.RawRes
19 import android.content.Context
20 import android.hardware.biometrics.BiometricFingerprintConstants
21 import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
22 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
23 import android.os.Bundle
24 import android.util.Log
25 import android.view.LayoutInflater
26 import android.view.Surface
27 import android.view.Surface.ROTATION_270
28 import android.view.Surface.ROTATION_90
29 import android.view.View
30 import android.view.ViewGroup
31 import android.widget.Button
32 import android.widget.ImageView
33 import android.widget.RelativeLayout
34 import android.widget.TextView
35 import androidx.activity.OnBackPressedCallback
36 import androidx.fragment.app.Fragment
37 import androidx.fragment.app.FragmentActivity
38 import androidx.lifecycle.Lifecycle
39 import androidx.lifecycle.MutableLiveData
40 import androidx.lifecycle.Observer
41 import androidx.lifecycle.ViewModelProvider
42 import androidx.lifecycle.lifecycleScope
43 import androidx.lifecycle.repeatOnLifecycle
44 import com.airbnb.lottie.LottieAnimationView
45 import com.airbnb.lottie.LottieComposition
46 import com.airbnb.lottie.LottieCompositionFactory
47 import com.android.settings.R
48 import com.android.settings.biometrics2.ui.model.EnrollmentProgress
49 import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
50 import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
51 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
52 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
53 import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
54 import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
55 import com.android.settingslib.display.DisplayDensityUtils
56 import kotlinx.coroutines.launch
57 import kotlin.math.roundToInt
58
59 /**
60 * Fragment is used to handle enrolling process for udfps
61 */
62 class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
63
64 private var _enrollingViewModel: FingerprintEnrollEnrollingViewModel? = null
65 private val enrollingViewModel: FingerprintEnrollEnrollingViewModel
66 get() = _enrollingViewModel!!
67
68 private var _rotationViewModel: DeviceRotationViewModel? = null
69 private val rotationViewModel: DeviceRotationViewModel
70 get() = _rotationViewModel!!
71
72 private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
73 private val progressViewModel: FingerprintEnrollProgressViewModel
74 get() = _progressViewModel!!
75
76 private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
77 private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
78 get() = _errorDialogViewModel!!
79
80 private var illustrationLottie: LottieAnimationView? = null
81
82 private var haveShownTipLottie = false
83 private var haveShownLeftEdgeLottie = false
84 private var haveShownRightEdgeLottie = false
85 private var haveShownCenterLottie = false
86 private var haveShownGuideLottie = false
87
88 private var enrollingView: RelativeLayout? = null
89
90 private val titleText: TextView
91 get() = enrollingView!!.findViewById(R.id.suc_layout_title)!!
92
93 private val subTitleText: TextView
94 get() = enrollingView!!.findViewById(R.id.sud_layout_subtitle)!!
95
96 private val udfpsEnrollView: UdfpsEnrollView
97 get() = enrollingView!!.findViewById(R.id.udfps_animation_view)!!
98
99 private val skipBtn: Button
100 get() = enrollingView!!.findViewById(R.id.skip_btn)!!
101
102 private val icon: ImageView
103 get() = enrollingView!!.findViewById(R.id.sud_layout_icon)!!
104
105 private val shouldShowLottie: Boolean
106 get() {
107 val displayDensity = DisplayDensityUtils(requireContext())
108 val currentDensityIndex: Int = displayDensity.currentIndexForDefaultDisplay
109 val currentDensity: Int =
110 displayDensity.defaultDisplayDensityValues[currentDensityIndex]
111 val defaultDensity: Int = displayDensity.defaultDensityForDefaultDisplay
112 return defaultDensity == currentDensity
113 }
114
115 private val isAccessibilityEnabled
116 get() = enrollingViewModel.isAccessibilityEnabled
117
118 private var rotation = -1
119
120 private var enrollingCancelSignal: Any? = null
121
122 private val onSkipClickListener = View.OnClickListener { _: View? ->
123 enrollingViewModel.setOnSkipPressed()
124 cancelEnrollment(true) // TODO Add test after b/273640000 fixed
125 }
126
127 private val progressObserver = Observer { progress: EnrollmentProgress? ->
128 if (progress != null && progress.steps >= 0) {
129 onEnrollmentProgressChange(progress)
130 }
131 }
132
133 private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
134 Log.d(TAG, "helpMessageObserver($helpMessage)")
135 helpMessage?.let { onEnrollmentHelp(it) }
136 }
137
138 private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
139 Log.d(TAG, "errorMessageObserver($errorMessage)")
140 errorMessage?.let { onEnrollmentError(it) }
141 }
142
143 private val canceledSignalObserver = Observer { canceledSignal: Any? ->
144 Log.d(TAG, "canceledSignalObserver($canceledSignal)")
145 canceledSignal?.let { onEnrollmentCanceled(it) }
146 }
147
148 private val acquireObserver =
149 Observer { isAcquiredGood: Boolean? -> isAcquiredGood?.let { onAcquired(it) } }
150
151 private val pointerDownObserver =
152 Observer { sensorId: Int? -> sensorId?.let { onPointerDown(it) } }
153
154 private val pointerUpObserver =
155 Observer { sensorId: Int? -> sensorId?.let { onPointerUp(it) } }
156
157 private val rotationObserver =
158 Observer { rotation: Int? -> rotation?.let { onRotationChanged(it) } }
159
160 private val onBackPressedCallback: OnBackPressedCallback =
161 object : OnBackPressedCallback(true) {
162 override fun handleOnBackPressed() {
163 isEnabled = false
164 enrollingViewModel.setOnBackPressed()
165 cancelEnrollment(true)
166 }
167 }
168
169 // Give the user a chance to see progress completed before jumping to the next stage.
170 private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() }
171
172 override fun onAttach(context: Context) {
173 ViewModelProvider(requireActivity()).let { provider ->
174 _enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
175 _rotationViewModel = provider[DeviceRotationViewModel::class.java]
176 _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
177 _errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
178 }
179 super.onAttach(context)
180 requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
181 }
182
183 override fun onDetach() {
184 onBackPressedCallback.isEnabled = false
185 super.onDetach()
186 }
187
188 override fun onCreateView(
189 inflater: LayoutInflater, container: ViewGroup?,
190 savedInstanceState: Bundle?
191 ): View = (inflater.inflate(
192 R.layout.udfps_enroll_enrolling_v2, container, false
193 ) as RelativeLayout).also {
194 enrollingView = it
195 }
196
197 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
198 super.onViewCreated(view, savedInstanceState)
199 rotation = rotationViewModel.liveData.value!!
200 updateIllustrationLottie(rotation)
201
202 requireActivity().bindFingerprintEnrollEnrollingUdfpsView(
203 view = enrollingView!!,
204 sensorProperties = enrollingViewModel.firstFingerprintSensorPropertiesInternal!!,
205 rotation = rotation,
206 onSkipClickListener = onSkipClickListener,
207 )
208
209 lifecycleScope.launch {
210 repeatOnLifecycle(Lifecycle.State.STARTED) {
211 errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
212 }
213 }
214 }
215
216 private fun retryEnrollment() {
217 reattachUdfpsEnrollView()
218
219 startEnrollment()
220
221 updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
222 progressViewModel.helpMessageLiveData.value.let {
223 if (it != null) {
224 onEnrollmentHelp(it)
225 } else {
226 updateTitleAndDescription()
227 }
228 }
229 }
230
231 override fun onStart() {
232 super.onStart()
233 val isEnrolling = progressViewModel.isEnrolling
234 val isErrorDialogShown = errorDialogViewModel.isDialogShown
235 Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
236 if (!isErrorDialogShown) {
237 startEnrollment()
238 }
239
240 updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
241 progressViewModel.helpMessageLiveData.value.let {
242 if (it != null) {
243 onEnrollmentHelp(it)
244 } else {
245 updateTitleAndDescription()
246 }
247 }
248 }
249
250 private fun reattachUdfpsEnrollView() {
251 enrollingView!!.let {
252 val newUdfpsView = LayoutInflater.from(requireActivity()).inflate(
253 R.layout.udfps_enroll_enrolling_v2_udfps_view,
254 null
255 )
256 val index = it.indexOfChild(udfpsEnrollView)
257 val lp = udfpsEnrollView.layoutParams
258
259 it.removeView(udfpsEnrollView)
260 it.addView(newUdfpsView, index, lp)
261 udfpsEnrollView.setSensorProperties(
262 enrollingViewModel.firstFingerprintSensorPropertiesInternal
263 )
264 }
265
266 // Clear lottie status
267 haveShownTipLottie = false
268 haveShownLeftEdgeLottie = false
269 haveShownRightEdgeLottie = false
270 haveShownCenterLottie = false
271 haveShownGuideLottie = false
272 illustrationLottie?.let {
273 it.contentDescription = ""
274 it.visibility = View.GONE
275 }
276 }
277
278 override fun onResume() {
279 super.onResume()
280 rotationViewModel.liveData.observe(this, rotationObserver)
281 }
282
283 override fun onPause() {
284 rotationViewModel.liveData.removeObserver(rotationObserver)
285 super.onPause()
286 }
287
288 override fun onStop() {
289 removeEnrollmentObservers()
290 val isEnrolling = progressViewModel.isEnrolling
291 val isConfigChange = requireActivity().isChangingConfigurations
292 Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
293 if (isEnrolling && !isConfigChange) {
294 cancelEnrollment(false)
295 }
296 super.onStop()
297 }
298
299 private fun removeEnrollmentObservers() {
300 progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
301 progressViewModel.progressLiveData.removeObserver(progressObserver)
302 progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
303 progressViewModel.acquireLiveData.removeObserver(acquireObserver)
304 progressViewModel.pointerDownLiveData.removeObserver(pointerDownObserver)
305 progressViewModel.pointerUpLiveData.removeObserver(pointerUpObserver)
306 }
307
308 private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
309 if (!progressViewModel.isEnrolling) {
310 Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
311 return
312 }
313 removeEnrollmentObservers()
314 if (waitForLastCancelErrMsg) {
315 progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
316 } else {
317 enrollingCancelSignal = null
318 }
319 val cancelResult: Boolean = progressViewModel.cancelEnrollment()
320 if (!cancelResult) {
321 Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
322 }
323 }
324
325 private fun startEnrollment() {
326 enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
327 if (enrollingCancelSignal == null) {
328 Log.e(TAG, "startEnrollment(), failed")
329 } else {
330 Log.d(TAG, "startEnrollment(), success")
331 }
332 progressViewModel.progressLiveData.observe(this, progressObserver)
333 progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
334 progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
335 progressViewModel.acquireLiveData.observe(this, acquireObserver)
336 progressViewModel.pointerDownLiveData.observe(this, pointerDownObserver)
337 progressViewModel.pointerUpLiveData.observe(this, pointerUpObserver)
338 }
339
340 private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) {
341 if (!progressViewModel.isEnrolling) {
342 Log.d(TAG, "Enrollment not started yet")
343 return
344 }
345
346 val progress = getProgress(enrollmentProgress)
347 Log.d(TAG, "updateProgress($animate, $enrollmentProgress), progress:$progress")
348
349 if (enrollmentProgress.steps != -1) {
350 udfpsEnrollView.onEnrollmentProgress(
351 enrollmentProgress.remaining,
352 enrollmentProgress.steps
353 )
354 }
355
356 if (progress >= PROGRESS_BAR_MAX) {
357 if (animate) {
358 // Wait animations to finish, then proceed to next page
359 activity!!.mainThreadHandler.postDelayed(delayedFinishRunnable, 400L)
360 } else {
361 delayedFinishRunnable.run()
362 }
363 }
364 }
365
366 private fun getProgress(progress: EnrollmentProgress): Int {
367 if (progress.steps == -1) {
368 return 0
369 }
370 val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining)
371 return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
372 }
373
374 private fun updateTitleAndDescription() {
375 Log.d(TAG, "updateTitleAndDescription($currentStage)")
376 when (currentStage) {
377 STAGE_CENTER -> {
378 titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
379 if (isAccessibilityEnabled || illustrationLottie == null) {
380 subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
381 } else if (!haveShownCenterLottie) {
382 haveShownCenterLottie = true
383 // Note: Update string reference when differentiate in between udfps & sfps
384 illustrationLottie!!.contentDescription = getString(R.string.security_settings_sfps_enroll_finger_center_title)
385 configureEnrollmentStage(R.raw.udfps_center_hint_lottie)
386 }
387 }
388
389 STAGE_GUIDED -> {
390 titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
391 if (isAccessibilityEnabled || illustrationLottie == null) {
392 subTitleText.setText(
393 R.string.security_settings_udfps_enroll_repeat_a11y_message
394 )
395 } else if (!haveShownGuideLottie) {
396 haveShownGuideLottie = true
397 illustrationLottie!!.contentDescription =
398 getString(R.string.security_settings_fingerprint_enroll_repeat_message)
399 // TODO(b/228100413) Could customize guided lottie animation
400 configureEnrollmentStage(R.raw.udfps_center_hint_lottie)
401 }
402 }
403
404 STAGE_FINGERTIP -> {
405 titleText.setText(R.string.security_settings_udfps_enroll_fingertip_title)
406 if (!haveShownTipLottie && illustrationLottie != null) {
407 haveShownTipLottie = true
408 illustrationLottie!!.contentDescription =
409 getString(R.string.security_settings_udfps_tip_fingerprint_help)
410 configureEnrollmentStage(R.raw.udfps_tip_hint_lottie)
411 }
412 }
413
414 STAGE_LEFT_EDGE -> {
415 titleText.setText(R.string.security_settings_udfps_enroll_left_edge_title)
416 if (!haveShownLeftEdgeLottie && illustrationLottie != null) {
417 haveShownLeftEdgeLottie = true
418 illustrationLottie!!.contentDescription =
419 getString(R.string.security_settings_udfps_side_fingerprint_help)
420 configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie)
421 } else if (illustrationLottie == null) {
422 if (isStageHalfCompleted) {
423 subTitleText.setText(
424 R.string.security_settings_fingerprint_enroll_repeat_message
425 )
426 } else {
427 subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message)
428 }
429 }
430 }
431
432 STAGE_RIGHT_EDGE -> {
433 titleText.setText(R.string.security_settings_udfps_enroll_right_edge_title)
434 if (!haveShownRightEdgeLottie && illustrationLottie != null) {
435 haveShownRightEdgeLottie = true
436 illustrationLottie!!.contentDescription =
437 getString(R.string.security_settings_udfps_side_fingerprint_help)
438 configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie)
439 } else if (illustrationLottie == null) {
440 if (isStageHalfCompleted) {
441 subTitleText.setText(
442 R.string.security_settings_fingerprint_enroll_repeat_message
443 )
444 } else {
445 subTitleText.setText(R.string.security_settings_udfps_enroll_edge_message)
446 }
447 }
448 }
449
450 STAGE_UNKNOWN -> {
451 titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title)
452 subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
453 val description: CharSequence = getString(
454 R.string.security_settings_udfps_enroll_a11y
455 )
456 requireActivity().title = description
457 }
458
459 else -> {
460 titleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title)
461 subTitleText.setText(R.string.security_settings_udfps_enroll_start_message)
462 val description: CharSequence = getString(
463 R.string.security_settings_udfps_enroll_a11y
464 )
465 requireActivity().title = description
466 }
467 }
468 }
469
470 private fun updateIllustrationLottie(@Surface.Rotation rotation: Int) {
471 if (rotation == ROTATION_90 || rotation == ROTATION_270) {
472 illustrationLottie = null
473 } else if (shouldShowLottie) {
474 illustrationLottie =
475 enrollingView!!.findViewById(R.id.illustration_lottie)
476 }
477 }
478
479 private val currentStage: Int
480 get() {
481 val progress = progressViewModel.progressLiveData.value!!
482 if (progress.steps == -1) {
483 return STAGE_UNKNOWN
484 }
485 val progressSteps: Int = progress.steps - progress.remaining
486 return if (progressSteps < getStageThresholdSteps(0)) {
487 STAGE_CENTER
488 } else if (progressSteps < getStageThresholdSteps(1)) {
489 STAGE_GUIDED
490 } else if (progressSteps < getStageThresholdSteps(2)) {
491 STAGE_FINGERTIP
492 } else if (progressSteps < getStageThresholdSteps(3)) {
493 STAGE_LEFT_EDGE
494 } else {
495 STAGE_RIGHT_EDGE
496 }
497 }
498
499 private val isStageHalfCompleted: Boolean
500 get() {
501 val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!!
502 if (progress.steps == -1) {
503 return false
504 }
505 val progressSteps: Int = progress.steps - progress.remaining
506 var prevThresholdSteps = 0
507 for (i in 0 until enrollingViewModel.getEnrollStageCount()) {
508 val thresholdSteps = getStageThresholdSteps(i)
509 if (progressSteps in prevThresholdSteps until thresholdSteps) {
510 val adjustedProgress = progressSteps - prevThresholdSteps
511 val adjustedThreshold = thresholdSteps - prevThresholdSteps
512 return adjustedProgress >= adjustedThreshold / 2
513 }
514 prevThresholdSteps = thresholdSteps
515 }
516
517 // After last enrollment step.
518 return true
519 }
520
521 private fun getStageThresholdSteps(index: Int): Int {
522 val progress: EnrollmentProgress = progressViewModel.progressLiveData.value!!
523 if (progress.steps == -1) {
524 Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet")
525 return 1
526 }
527 return (progress.steps * enrollingViewModel.getEnrollStageThreshold(index)).roundToInt()
528 }
529
530 private fun configureEnrollmentStage(@RawRes lottie: Int) {
531 subTitleText.text = ""
532 LottieCompositionFactory.fromRawRes(activity, lottie)
533 .addListener { c: LottieComposition ->
534 illustrationLottie?.let {
535 it.setComposition(c)
536 it.visibility = View.VISIBLE
537 it.playAnimation()
538 }
539 }
540 }
541
542 private fun onEnrollmentProgressChange(progress: EnrollmentProgress) {
543 updateProgress(true /* animate */, progress)
544 updateTitleAndDescription()
545 if (isAccessibilityEnabled) {
546 val steps: Int = progress.steps
547 val remaining: Int = progress.remaining
548 val percent = ((steps - remaining).toFloat() / steps.toFloat() * 100).toInt()
549 val announcement: CharSequence = activity!!.getString(
550 R.string.security_settings_udfps_enroll_progress_a11y_message, percent
551 )
552 enrollingViewModel.sendAccessibilityEvent(announcement)
553 }
554 }
555
556 private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
557 Log.d(TAG, "onEnrollmentHelp($helpMessage)")
558 val helpStr: CharSequence = helpMessage.str
559 if (helpStr.isNotEmpty()) {
560 showError(helpStr)
561 udfpsEnrollView.onEnrollmentHelp()
562 }
563 }
564
565 private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
566 cancelEnrollment(true)
567 lifecycleScope.launch {
568 Log.d(TAG, "newDialog $errorMessage")
569 errorDialogViewModel.newDialog(errorMessage.msgId)
570 }
571 }
572
573 private fun onEnrollmentCanceled(canceledSignal: Any) {
574 Log.d(
575 TAG,
576 "onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
577 )
578 if (enrollingCancelSignal === canceledSignal) {
579 progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
580 progressViewModel.clearProgressLiveData()
581 if (enrollingViewModel.onBackPressed) {
582 enrollingViewModel.onCancelledDueToOnBackPressed()
583 } else if (enrollingViewModel.onSkipPressed) {
584 enrollingViewModel.onCancelledDueToOnSkipPressed()
585 }
586 }
587 }
588
589 private fun onAcquired(isAcquiredGood: Boolean) {
590 udfpsEnrollView.onAcquired(isAcquiredGood)
591 }
592
593 private fun onPointerDown(sensorId: Int) {
594 udfpsEnrollView.onPointerDown(sensorId)
595 }
596
597 private fun onPointerUp(sensorId: Int) {
598 udfpsEnrollView.onPointerUp(sensorId)
599 }
600
601 private fun showError(error: CharSequence) {
602 titleText.text = error
603 titleText.contentDescription = error
604 subTitleText.contentDescription = ""
605 }
606
607 private fun onRotationChanged(newRotation: Int) {
608 if ((newRotation + 2) % 4 == rotation) {
609 rotation = newRotation
610 requireContext().configLayout(newRotation, titleText, subTitleText, icon, skipBtn)
611 }
612 }
613
614 companion object {
615 private val TAG = "FingerprintEnrollEnrollingUdfpsFragment"
616 private const val PROGRESS_BAR_MAX = 10000
617 private const val STAGE_UNKNOWN = -1
618 private const val STAGE_CENTER = 0
619 private const val STAGE_GUIDED = 1
620 private const val STAGE_FINGERTIP = 2
621 private const val STAGE_LEFT_EDGE = 3
622 private const val STAGE_RIGHT_EDGE = 4
623 }
624 }
625
626
FragmentActivitynull627 fun FragmentActivity.bindFingerprintEnrollEnrollingUdfpsView(
628 view: RelativeLayout,
629 sensorProperties: FingerprintSensorPropertiesInternal,
630 @Surface.Rotation rotation: Int,
631 onSkipClickListener: View.OnClickListener
632 ) {
633 view.findViewById<UdfpsEnrollView>(R.id.udfps_animation_view)!!.setSensorProperties(
634 sensorProperties
635 )
636
637 val titleText = view.findViewById<TextView>(R.id.suc_layout_title)!!
638 val subTitleText = view.findViewById<TextView>(R.id.sud_layout_subtitle)!!
639 val icon = view.findViewById<ImageView>(R.id.sud_layout_icon)!!
640 val skipBtn = view.findViewById<Button>(R.id.skip_btn)!!.also {
641 it.setOnClickListener(onSkipClickListener)
642 }
643 configLayout(rotation, titleText, subTitleText, icon, skipBtn)
644 }
645
Contextnull646 private fun Context.configLayout(
647 @Surface.Rotation newRotation: Int,
648 titleText: TextView,
649 subTitleText: TextView,
650 icon: ImageView,
651 skipBtn: Button
652 ) {
653 if (newRotation == ROTATION_270) {
654 val iconLP = RelativeLayout.LayoutParams(-2, -2)
655 iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
656 iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
657 iconLP.topMargin = convertDpToPixel(76.64f)
658 iconLP.leftMargin = convertDpToPixel(151.54f)
659 icon.layoutParams = iconLP
660 val titleLP = RelativeLayout.LayoutParams(-1, -2)
661 titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
662 titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
663 titleLP.topMargin = convertDpToPixel(138f)
664 titleLP.leftMargin = convertDpToPixel(144f)
665 titleText.layoutParams = titleLP
666 val subtitleLP = RelativeLayout.LayoutParams(-1, -2)
667 subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
668 subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view)
669 subtitleLP.topMargin = convertDpToPixel(198f)
670 subtitleLP.leftMargin = convertDpToPixel(144f)
671 subTitleText.layoutParams = subtitleLP
672 } else if (newRotation == ROTATION_90) {
673 val metrics = resources.displayMetrics
674 val iconLP = RelativeLayout.LayoutParams(-2, -2)
675 iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
676 iconLP.addRule(RelativeLayout.ALIGN_PARENT_START)
677 iconLP.topMargin = convertDpToPixel(76.64f)
678 iconLP.leftMargin = convertDpToPixel(71.99f)
679 icon.layoutParams = iconLP
680 val titleLP = RelativeLayout.LayoutParams(
681 metrics.widthPixels / 2, -2
682 )
683 titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
684 titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view)
685 titleLP.topMargin = convertDpToPixel(138f)
686 titleLP.leftMargin = convertDpToPixel(66f)
687 titleText.layoutParams = titleLP
688 val subtitleLP = RelativeLayout.LayoutParams(
689 metrics.widthPixels / 2, -2
690 )
691 subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP)
692 subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START)
693 subtitleLP.topMargin = convertDpToPixel(198f)
694 subtitleLP.leftMargin = convertDpToPixel(66f)
695 subTitleText.layoutParams = subtitleLP
696 }
697 if (newRotation == ROTATION_90 || newRotation == ROTATION_270) {
698 val skipBtnLP = skipBtn.layoutParams as RelativeLayout.LayoutParams
699 skipBtnLP.topMargin = convertDpToPixel(26f)
700 skipBtnLP.leftMargin = convertDpToPixel(54f)
701 skipBtn.requestLayout()
702 }
703 }
704
Contextnull705 fun Context.convertDpToPixel(dp: Float): Int {
706 return (dp * resources.displayMetrics.density).toInt()
707 }
708