Files
app_Settings/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt
Milton Wu 75450f7973 [BiometricsV2] Refactor FindSfpsFragment
Refactor FingerprintEnrollFindSfpsFragment to kotlin and add bindView()
method for it

Bug: 286197831
Test: atest FingerprintEnrollmentActivityTest
Test: Manually test enrollment as Side fingerpint device
Change-Id: If1c7d95e78c5be237f05209afa3ffc0b4c444c61
2023-06-21 09:56:32 +08:00

311 lines
12 KiB
Kotlin

/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.biometrics2.ui.view
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RawRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.airbnb.lottie.LottieAnimationView
import com.android.settings.R
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
import com.android.settingslib.widget.LottieColorUtils
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
/**
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
* It interacts with ProgressViewModel, FoldCallback (for different lottie), and
* LottieAnimationView.
* <pre>
* | Has | UDFPS | SFPS | Other (Rear FPS) |
* |---------------------|-------|------|------------------|
* | Primary button | Yes | No | No |
* | Illustration Lottie | Yes | Yes | No |
* | Animation | No | No | Depend on layout |
* | Progress ViewModel | No | Yes | Yes |
* | Orientation detect | No | Yes | No |
* | Foldable detect | No | Yes | No |
* </pre>
*/
class FingerprintEnrollFindSfpsFragment : Fragment() {
private var _viewModel: FingerprintEnrollFindSensorViewModel? = null
private val viewModel: FingerprintEnrollFindSensorViewModel
get() = _viewModel!!
private var _progressViewModel: FingerprintEnrollProgressViewModel? = null
private val progressViewModel: FingerprintEnrollProgressViewModel
get() = _progressViewModel!!
private var _rotationViewModel: DeviceRotationViewModel? = null
private val rotationViewModel: DeviceRotationViewModel
get() = _rotationViewModel!!
private var _foldedViewModel: DeviceFoldedViewModel? = null
private val foldedViewModel: DeviceFoldedViewModel
get() = _foldedViewModel!!
private var findSfpsView: GlifLayout? = null
private val onSkipClickListener =
View.OnClickListener { _: View? -> viewModel.onSkipButtonClick() }
private val illustrationLottie: LottieAnimationView
get() = findSfpsView!!.findViewById<LottieAnimationView>(R.id.illustration_lottie)!!
@Surface.Rotation
private var animationRotation = -1
private val rotationObserver = Observer { rotation: Int? ->
if (DEBUG) {
Log.d(TAG, "rotationObserver $rotation")
}
rotation?.let { onRotationChanged(it) }
}
private val progressObserver: Observer<EnrollmentProgress> =
Observer<EnrollmentProgress> { progress: EnrollmentProgress? ->
if (DEBUG) {
Log.d(TAG, "progressObserver($progress)")
}
if (progress != null && !progress.isInitialStep) {
stopLookingForFingerprint(true)
}
}
private val lastCancelMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "lastCancelMessageObserver($errorMessage)")
}
errorMessage?.let { onLastCancelMessage(it) }
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View = (inflater.inflate(
R.layout.sfps_enroll_find_sensor_layout,
container,
false
) as GlifLayout).also {
findSfpsView = it
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().bindFingerprintEnrollFindSfpsView(
view = findSfpsView!!,
onSkipClickListener = onSkipClickListener
)
}
override fun onStart() {
super.onStart()
val isEnrolling: Boolean = progressViewModel.isEnrolling
if (DEBUG) {
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling")
}
if (!isEnrolling) {
startLookingForFingerprint()
}
}
override fun onResume() {
super.onResume()
val rotationLiveData: LiveData<Int> = rotationViewModel.liveData
playLottieAnimation(rotationLiveData.value!!)
rotationLiveData.observe(this, rotationObserver)
}
override fun onPause() {
rotationViewModel.liveData.removeObserver(rotationObserver)
super.onPause()
}
override fun onStop() {
super.onStop()
val isEnrolling: Boolean = progressViewModel.isEnrolling
if (DEBUG) {
Log.d(TAG, "onStop(), isEnrolling:$isEnrolling")
}
if (isEnrolling) {
stopLookingForFingerprint(false)
}
}
private fun startLookingForFingerprint() {
if (progressViewModel.isEnrolling) {
Log.d(
TAG,
"startLookingForFingerprint(), failed because isEnrolling is true before starting"
)
return
}
progressViewModel.clearProgressLiveData()
progressViewModel.progressLiveData.observe(this, progressObserver)
val startResult: Boolean =
progressViewModel.startEnrollment(FingerprintManager.ENROLL_FIND_SENSOR)
if (!startResult) {
Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment")
}
}
private fun stopLookingForFingerprint(waitForLastCancelErrMsg: Boolean) {
if (!progressViewModel.isEnrolling) {
Log.d(
TAG, "stopLookingForFingerprint(), failed because isEnrolling is false before"
+ " stopping"
)
return
}
if (waitForLastCancelErrMsg) {
progressViewModel.clearErrorMessageLiveData() // Prevent got previous error message
progressViewModel.errorMessageLiveData.observe(
this,
lastCancelMessageObserver
)
}
progressViewModel.progressLiveData.removeObserver(progressObserver)
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
if (!cancelResult) {
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment")
}
}
private fun onRotationChanged(@Surface.Rotation newRotation: Int) {
if (DEBUG) {
Log.d(TAG, "onRotationChanged() from $animationRotation to $newRotation")
}
if ((newRotation + 2) % 4 == animationRotation) {
// Fragment not changed, we just need to play correct rotation animation
playLottieAnimation(newRotation)
} else if (newRotation % 2 != animationRotation % 2) {
// Fragment is going to be recreated, just stopLookingForFingerprint() here.
stopLookingForFingerprint(true)
}
}
private fun onLastCancelMessage(errorMessage: EnrollmentStatusMessage) {
if (errorMessage.msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
val progress: EnrollmentProgress? = progressViewModel.progressLiveData.value
progressViewModel.clearProgressLiveData()
progressViewModel.errorMessageLiveData.removeObserver(lastCancelMessageObserver)
if (progress != null && !progress.isInitialStep) {
viewModel.onStartButtonClick()
}
} else {
Log.e(TAG, "errorMessageObserver($errorMessage)")
}
}
private fun playLottieAnimation(@Surface.Rotation rotation: Int) {
@RawRes val animationRawRes = getSfpsLottieAnimationRawRes(rotation)
if (DEBUG) {
Log.d(
TAG,
"play lottie animation $animationRawRes, previous rotation:$animationRotation"
+ ", new rotation:" + rotation
)
}
animationRotation = rotation
illustrationLottie.setAnimation(animationRawRes)
LottieColorUtils.applyDynamicColors(activity, illustrationLottie)
illustrationLottie.visibility = View.VISIBLE
illustrationLottie.playAnimation()
}
@RawRes
private fun getSfpsLottieAnimationRawRes(@Surface.Rotation rotation: Int): Int {
val isFolded = java.lang.Boolean.FALSE != foldedViewModel.getLiveData().getValue()
return when (rotation) {
Surface.ROTATION_90 ->
if (isFolded)
R.raw.fingerprint_edu_lottie_folded_top_left
else
R.raw.fingerprint_edu_lottie_portrait_top_left
Surface.ROTATION_180 ->
if (isFolded)
R.raw.fingerprint_edu_lottie_folded_bottom_left
else
R.raw.fingerprint_edu_lottie_landscape_bottom_left
Surface.ROTATION_270 ->
if (isFolded)
R.raw.fingerprint_edu_lottie_folded_bottom_right
else
R.raw.fingerprint_edu_lottie_portrait_bottom_right
else ->
if (isFolded)
R.raw.fingerprint_edu_lottie_folded_top_right
else
R.raw.fingerprint_edu_lottie_landscape_top_right
}
}
override fun onAttach(context: Context) {
ViewModelProvider(requireActivity()).let { provider ->
_viewModel = provider[FingerprintEnrollFindSensorViewModel::class.java]
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
_foldedViewModel = provider[DeviceFoldedViewModel::class.java]
}
super.onAttach(context)
}
companion object {
private const val DEBUG = false
private const val TAG = "FingerprintEnrollFindSfpsFragment"
}
}
fun FragmentActivity.bindFingerprintEnrollFindSfpsView(
view: GlifLayout,
onSkipClickListener: View.OnClickListener
) {
view.getMixin(FooterBarMixin::class.java).let {
it.secondaryButton = FooterButton.Builder(this)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
it.secondaryButton.setOnClickListener(onSkipClickListener)
}
GlifLayoutHelper(this, view).let {
it.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
it.setDescriptionText(
getText(R.string.security_settings_sfps_enroll_find_sensor_message)
)
}
}