[BiometricsV2] Add retry button

Add retry button for FingerprintEnrollErrorDialog and make sure that
this button works well in the whole enrollment flow.

Bug: 287168522
Test: manually test this dialog with error and rotate devices
Test: atest FingerprintEnrollEnrollingViewModelTest
Test: atest FingerprintEnrollErrorDialogViewModelTest
Test: atest FingerprintEnrollProgressViewModelTest
Test: atest FingerprintEnrollmentActivityTest
Test: atest biometrics-enrollment-test

Change-Id: Ica1d91d077ca322caca5551068f2a3c23b544361
This commit is contained in:
Milton Wu
2023-06-28 21:51:53 +08:00
parent a372258805
commit f94932801a
21 changed files with 1138 additions and 729 deletions

View File

@@ -77,6 +77,7 @@ android_library {
"setupcompat",
"setupdesign",
"androidx.lifecycle_lifecycle-runtime",
"androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel",
"guava",
"jsr305",

View File

@@ -70,25 +70,7 @@
app:lottie_loop="true"
app:lottie_speed=".85" />
<com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
android:id="@+id/udfps_animation_view"
android:layout_width="218.42dp"
android:layout_height="216dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="553dp">
<ImageView
android:id="@+id/udfps_enroll_animation_fp_progress_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Fingerprint -->
<ImageView
android:id="@+id/udfps_enroll_animation_fp_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.settings.biometrics2.ui.widget.UdfpsEnrollView>
<include layout="@layout/udfps_enroll_enrolling_v2_udfps_view"/>
<Button
style="@style/SudGlifButton.Secondary"

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2023 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.
-->
<com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/udfps_animation_view"
android:layout_width="218.42dp"
android:layout_height="216dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="553dp">
<ImageView
android:id="@+id/udfps_enroll_animation_fp_progress_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- Fingerprint -->
<ImageView
android:id="@+id/udfps_enroll_animation_fp_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.settings.biometrics2.ui.widget.UdfpsEnrollView>

View File

@@ -34,6 +34,7 @@ import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.Cha
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel;
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel;
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel;
@@ -47,7 +48,7 @@ import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
*/
public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
private static final String TAG = "BiometricsViewModelFact";
private static final String TAG = "BiometricsViewModelFactory";
public static final CreationExtras.Key<ChallengeGenerator> CHALLENGE_GENERATOR_KEY =
new CreationExtras.Key<ChallengeGenerator>() {};
@@ -113,7 +114,7 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
final Integer userId = extras.get(USER_ID_KEY);
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
application);
if (fingerprint != null) {
if (fingerprint != null && userId != null) {
return (T) new FingerprintEnrollEnrollingViewModel(application, userId,
fingerprint);
}
@@ -122,10 +123,15 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
application);
if (fingerprint != null && userId != null) {
if (fingerprint != null && userId != null && request != null) {
return (T) new FingerprintEnrollFinishViewModel(application, userId, request,
fingerprint);
}
} else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) {
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
if (request != null) {
return (T) new FingerprintEnrollErrorDialogViewModel(application, request.isSuw());
}
}
return create(modelClass);
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2023 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.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.hardware.biometrics.BiometricConstants
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import com.android.settings.R
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
/**
* Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment.
*/
class FingerprintEnrollEnrollingErrorDialog : DialogFragment() {
private var mViewModel: FingerprintEnrollEnrollingViewModel? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val value = mViewModel!!.errorDialogLiveData.value!!
return requireActivity().bindFingerprintEnrollEnrollingErrorDialog(
title = value.errTitle,
message = value.errMsg,
positiveButtonClickListener = { dialog: DialogInterface?, _: Int ->
dialog?.dismiss()
mViewModel?.onErrorDialogAction(
if (value.errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT)
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
else
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
)
}
)
}
override fun onAttach(context: Context) {
mViewModel = ViewModelProvider(requireActivity())[
FingerprintEnrollEnrollingViewModel::class.java]
super.onAttach(context)
}
}
fun Context.bindFingerprintEnrollEnrollingErrorDialog(
title: CharSequence?,
message: CharSequence?,
positiveButtonClickListener: DialogInterface.OnClickListener
): AlertDialog = AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setCancelable(false)
.setPositiveButton(
R.string.security_settings_fingerprint_enroll_dialog_ok,
positiveButtonClickListener
)
.create()
.apply { setCanceledOnTouchOutside(false) }

View File

@@ -23,13 +23,14 @@ import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_CANCELED
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils.loadInterpolator
@@ -39,17 +40,22 @@ import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.launch
/**
* Fragment is used to handle enrolling process for rfps
@@ -64,25 +70,24 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
private val progressViewModel: FingerprintEnrollProgressViewModel
get() = _progressViewModel!!
private val fastOutSlowInInterpolator: Interpolator
get() = loadInterpolator(requireActivity(), android.R.interpolator.fast_out_slow_in)
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
get() = _errorDialogViewModel!!
private val linearOutSlowInInterpolator: Interpolator
get() = loadInterpolator(requireActivity(), android.R.interpolator.linear_out_slow_in)
private val fastOutLinearInInterpolator: Interpolator
get() = loadInterpolator(requireActivity(), android.R.interpolator.fast_out_linear_in)
private var fastOutSlowInInterpolator: Interpolator? = null
private var linearOutSlowInInterpolator: Interpolator? = null
private var fastOutLinearInInterpolator: Interpolator? = null
private var isAnimationCancelled = false
private var enrollingRfpsView: GlifLayout? = null
private var enrollingView: GlifLayout? = null
private val progressBar: ProgressBar
get() = enrollingRfpsView!!.findViewById<ProgressBar>(R.id.fingerprint_progress_bar)!!
get() = enrollingView!!.findViewById(R.id.fingerprint_progress_bar)!!
private var progressAnim: ObjectAnimator? = null
private val errorText: TextView
get() = enrollingRfpsView!!.findViewById<TextView>(R.id.error_text)!!
get() = enrollingView!!.findViewById(R.id.error_text)!!
private val iconAnimationDrawable: AnimatedVectorDrawable?
get() = (progressBar.background as LayerDrawable)
@@ -94,53 +99,47 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
private var iconTouchCount = 0
private val touchAgainRunnable =
Runnable {
showError(
// Use enrollingRfpsView to getString to prevent activity is missing during rotation
enrollingRfpsView!!.context.getString(
R.string.security_settings_fingerprint_enroll_lift_touch_again
)
private val touchAgainRunnable = Runnable {
showError(
// Use enrollingView to getString to prevent activity is missing during rotation
enrollingView!!.context.getString(
R.string.security_settings_fingerprint_enroll_lift_touch_again
)
}
)
}
private val onSkipClickListener = View.OnClickListener { _: View? ->
enrollingViewModel.setOnSkipPressed()
cancelEnrollment()
cancelEnrollment(true)
}
private val progressObserver: Observer<EnrollmentProgress> =
Observer<EnrollmentProgress> { progress: EnrollmentProgress? ->
if (DEBUG) {
Log.d(TAG, "progressObserver($progress)")
}
if (progress != null && progress.steps >= 0) {
onEnrollmentProgressChange(progress)
}
}
private var enrollingCancelSignal: Any? = null
private val helpMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { helpMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "helpMessageObserver($helpMessage)")
}
helpMessage?.let { onEnrollmentHelp(it) }
private val progressObserver = Observer { progress: EnrollmentProgress? ->
if (progress != null && progress.steps >= 0) {
onEnrollmentProgressChange(progress)
}
}
private val errorMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "errorMessageObserver($errorMessage)")
}
errorMessage?.let { onEnrollmentError(it) }
}
private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
helpMessage?.let { onEnrollmentHelp(it) }
}
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
Log.d(TAG, "errorMessageObserver($errorMessage)")
errorMessage?.let { onEnrollmentError(it) }
}
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
canceledSignal?.let { onEnrollmentCanceled(it) }
}
private val onBackPressedCallback: OnBackPressedCallback =
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
isEnabled = false
enrollingViewModel.setOnBackPressed()
cancelEnrollment()
cancelEnrollment(true)
}
}
@@ -148,6 +147,7 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
ViewModelProvider(requireActivity()).let { provider ->
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
}
super.onAttach(context)
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
@@ -162,10 +162,10 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
enrollingRfpsView = inflater.inflate(
enrollingView = inflater.inflate(
R.layout.fingerprint_enroll_enrolling, container, false
) as GlifLayout
return enrollingRfpsView!!
return enrollingView!!
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -193,16 +193,46 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
}
requireActivity().bindFingerprintEnrollEnrollingRfpsView(
view = enrollingRfpsView!!,
view = enrollingView!!,
onSkipClickListener = onSkipClickListener
)
fastOutSlowInInterpolator =
loadInterpolator(requireContext(), android.R.interpolator.fast_out_slow_in)
linearOutSlowInInterpolator =
loadInterpolator(requireContext(), android.R.interpolator.linear_out_slow_in)
fastOutLinearInInterpolator =
loadInterpolator(requireContext(), android.R.interpolator.fast_out_linear_in)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
}
}
}
private fun retryEnrollment() {
isAnimationCancelled = false
startIconAnimation()
startEnrollment()
clearError()
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
updateTitleAndDescription()
}
override fun onStart() {
super.onStart()
isAnimationCancelled = false
startIconAnimation()
startEnrollment()
val isEnrolling = progressViewModel.isEnrolling
val isErrorDialogShown = errorDialogViewModel.isDialogShown
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
if (!isErrorDialogShown) {
isAnimationCancelled = false
startIconAnimation()
startEnrollment()
}
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
updateTitleAndDescription()
}
@@ -219,32 +249,44 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
override fun onStop() {
stopIconAnimation()
removeEnrollmentObservers()
if (!activity!!.isChangingConfigurations && progressViewModel.isEnrolling) {
progressViewModel.cancelEnrollment()
val isEnrolling = progressViewModel.isEnrolling
val isConfigChange = requireActivity().isChangingConfigurations
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
if (isEnrolling && !isConfigChange) {
cancelEnrollment(false)
}
super.onStop()
}
private fun removeEnrollmentObservers() {
preRemoveEnrollmentObservers()
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
}
private fun preRemoveEnrollmentObservers() {
progressViewModel.progressLiveData.removeObserver(progressObserver)
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
}
private fun cancelEnrollment() {
preRemoveEnrollmentObservers()
progressViewModel.cancelEnrollment()
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
if (!progressViewModel.isEnrolling) {
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
return
}
removeEnrollmentObservers()
if (waitForLastCancelErrMsg) {
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
} else {
enrollingCancelSignal = null
}
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
if (!cancelResult) {
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
}
}
private fun startEnrollment() {
val startResult: Boolean =
progressViewModel.startEnrollment(FingerprintManager.ENROLL_ENROLL)
if (!startResult) {
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
if (enrollingCancelSignal == null) {
Log.e(TAG, "startEnrollment(), failed")
} else {
Log.d(TAG, "startEnrollment(), success")
}
progressViewModel.progressLiveData.observe(this, progressObserver)
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
@@ -252,6 +294,7 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
}
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
Log.d(TAG, "onEnrollmentHelp($helpMessage)")
val helpStr: CharSequence = helpMessage.str
if (!TextUtils.isEmpty(helpStr)) {
errorText.removeCallbacks(touchAgainRunnable)
@@ -261,29 +304,27 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
stopIconAnimation()
removeEnrollmentObservers()
if (enrollingViewModel.onBackPressed
&& errorMessage.msgId == FINGERPRINT_ERROR_CANCELED
) {
enrollingViewModel.onCancelledDueToOnBackPressed()
} else if (enrollingViewModel.onSkipPressed
&& errorMessage.msgId == FINGERPRINT_ERROR_CANCELED
) {
enrollingViewModel.onCancelledDueToOnSkipPressed()
} else {
val errMsgId: Int = errorMessage.msgId
enrollingViewModel.showErrorDialog(
FingerprintEnrollEnrollingViewModel.ErrorDialogData(
enrollingRfpsView!!.context.getString(
FingerprintErrorDialog.getErrorMessage(errMsgId)
),
enrollingRfpsView!!.context.getString(
FingerprintErrorDialog.getErrorTitle(errMsgId)
),
errMsgId
)
)
progressViewModel.cancelEnrollment()
cancelEnrollment(true)
lifecycleScope.launch {
Log.d(TAG, "newDialog $errorMessage")
errorDialogViewModel.newDialog(errorMessage.msgId)
}
}
private fun onEnrollmentCanceled(canceledSignal: Any) {
Log.d(
TAG,
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
)
if (enrollingCancelSignal === canceledSignal) {
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
progressViewModel.clearProgressLiveData()
if (enrollingViewModel.onBackPressed) {
enrollingViewModel.onCancelledDueToOnBackPressed()
} else if (enrollingViewModel.onSkipPressed) {
enrollingViewModel.onCancelledDueToOnSkipPressed()
}
}
}
@@ -296,11 +337,10 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
}
private fun updateProgress(animate: Boolean, enrollmentProgress: EnrollmentProgress) {
if (!progressViewModel.isEnrolling) {
Log.d(TAG, "Enrollment not started yet")
return
}
val progress = getProgress(enrollmentProgress)
Log.d(TAG, "updateProgress($animate, $enrollmentProgress), old:${progressBar.progress}"
+ ", new:$progress")
// Only clear the error when progress has been made.
// TODO (b/234772728) Add tests.
if (progressBar.progress < progress) {
@@ -328,7 +368,7 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
errorText.text = error
if (errorText.visibility == View.INVISIBLE) {
errorText.visibility = View.VISIBLE
errorText.translationY = enrollingRfpsView!!.context.resources.getDimensionPixelSize(
errorText.translationY = enrollingView!!.context.resources.getDimensionPixelSize(
R.dimen.fingerprint_error_text_appear_distance
).toFloat()
errorText.alpha = 0f
@@ -359,7 +399,7 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
)
.setDuration(100)
.setInterpolator(fastOutLinearInInterpolator)
.withEndAction { errorText!!.visibility = View.INVISIBLE }
.withEndAction { errorText.visibility = View.INVISIBLE }
.start()
}
}
@@ -385,8 +425,8 @@ class FingerprintEnrollEnrollingRfpsFragment : Fragment() {
private fun updateTitleAndDescription() {
val progressLiveData: EnrollmentProgress = progressViewModel.progressLiveData.value!!
GlifLayoutHelper(activity!!, enrollingRfpsView!!).setDescriptionText(
enrollingRfpsView!!.context.getString(
GlifLayoutHelper(activity!!, enrollingView!!).setDescriptionText(
enrollingView!!.context.getString(
if (progressLiveData.steps == -1)
R.string.security_settings_fingerprint_enroll_start_message
else

View File

@@ -21,10 +21,11 @@ import android.annotation.RawRes
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.ColorFilter
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.hardware.fingerprint.FingerprintManager
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_CANCELED
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@@ -39,24 +40,29 @@ import android.widget.RelativeLayout
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
import com.google.android.setupdesign.template.DescriptionMixin
import com.google.android.setupdesign.template.HeaderMixin
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
/**
@@ -72,13 +78,17 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
private val progressViewModel: FingerprintEnrollProgressViewModel
get() = _progressViewModel!!
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
get() = _errorDialogViewModel!!
private val fastOutSlowInInterpolator: Interpolator
get() = AnimationUtils.loadInterpolator(activity, R.interpolator.fast_out_slow_in)
private var enrollingSfpsView: GlifLayout? = null
private var enrollingView: GlifLayout? = null
private val progressBar: ProgressBar
get() = enrollingSfpsView!!.findViewById<ProgressBar>(R.id.fingerprint_progress_bar)!!
get() = enrollingView!!.findViewById(R.id.fingerprint_progress_bar)!!
private var progressAnim: ObjectAnimator? = null
@@ -96,7 +106,7 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
private val illustrationLottie: LottieAnimationView
get() = enrollingSfpsView!!.findViewById<LottieAnimationView>(R.id.illustration_lottie)!!
get() = enrollingView!!.findViewById(R.id.illustration_lottie)!!
private var haveShownSfpsNoAnimationLottie = false
private var haveShownSfpsCenterLottie = false
@@ -110,78 +120,82 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
private val showIconTouchDialogRunnable = Runnable { showIconTouchDialog() }
private var enrollingCancelSignal: Any? = null
// Give the user a chance to see progress completed before jumping to the next stage.
private val delayedFinishRunnable = Runnable { enrollingViewModel.onEnrollingDone() }
private val onSkipClickListener = View.OnClickListener { _: View? ->
enrollingViewModel.setOnSkipPressed()
cancelEnrollment()
cancelEnrollment(true)
}
private val progressObserver: Observer<EnrollmentProgress> =
Observer<EnrollmentProgress> { progress: EnrollmentProgress? ->
if (DEBUG) {
Log.d(TAG, "progressObserver($progress)")
}
if (progress != null && progress.steps >= 0) {
onEnrollmentProgressChange(progress)
}
private val progressObserver = Observer { progress: EnrollmentProgress? ->
if (progress != null && progress.steps >= 0) {
onEnrollmentProgressChange(progress)
}
}
private val helpMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { helpMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "helpMessageObserver($helpMessage)")
}
helpMessage?.let { onEnrollmentHelp(it) }
}
private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
helpMessage?.let { onEnrollmentHelp(it) }
}
private val errorMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "errorMessageObserver($errorMessage)")
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
Log.d(TAG, "errorMessageObserver($errorMessage)")
errorMessage?.let { onEnrollmentError(it) }
}
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
Log.d(TAG, "canceledSignalObserver($canceledSignal)")
canceledSignal?.let { onEnrollmentCanceled(it) }
}
private val onBackPressedCallback: OnBackPressedCallback =
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
isEnabled = false
enrollingViewModel.setOnBackPressed()
cancelEnrollment(true)
}
errorMessage?.let { onEnrollmentError(it) }
}
override fun onAttach(context: Context) {
ViewModelProvider(requireActivity()).let { provider ->
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
}
super.onAttach(context)
requireActivity().onBackPressedDispatcher.addCallback(
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
isEnabled = false
enrollingViewModel.setOnBackPressed()
cancelEnrollment()
}
})
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
}
override fun onDetach() {
onBackPressedCallback.isEnabled = false
super.onDetach()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
enrollingSfpsView = inflater.inflate(
enrollingView = inflater.inflate(
R.layout.sfps_enroll_enrolling,
container, false
) as GlifLayout
return enrollingSfpsView
return enrollingView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().bindFingerprintEnrollEnrollingSfpsView(
view = enrollingSfpsView!!,
view = enrollingView!!,
onSkipClickListener = onSkipClickListener
)
// setHelpAnimation()
helpAnimation = ObjectAnimator.ofFloat(
enrollingSfpsView!!.findViewById<RelativeLayout>(R.id.progress_lottie)!!,
enrollingView!!.findViewById<RelativeLayout>(R.id.progress_lottie)!!,
"translationX" /* propertyName */,
0f,
HELP_ANIMATION_TRANSLATION_X,
@@ -212,48 +226,79 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
true
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
}
}
}
private fun retryEnrollment() {
startEnrollment()
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
}
override fun onStart() {
super.onStart()
startEnrollment()
val isEnrolling = progressViewModel.isEnrolling
val isErrorDialogShown = errorDialogViewModel.isDialogShown
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
if (!isErrorDialogShown) {
startEnrollment()
}
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
progressViewModel.helpMessageLiveData.value?.let {
onEnrollmentHelp(it)
} ?: run {
clearError()
updateTitleAndDescription()
progressViewModel.helpMessageLiveData.value.let {
if (it != null) {
onEnrollmentHelp(it)
} else {
clearError()
updateTitleAndDescription()
}
}
}
override fun onStop() {
removeEnrollmentObservers()
if (!activity!!.isChangingConfigurations && progressViewModel.isEnrolling) {
progressViewModel.cancelEnrollment()
val isEnrolling = progressViewModel.isEnrolling
val isConfigChange = requireActivity().isChangingConfigurations
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
if (isEnrolling && !isConfigChange) {
cancelEnrollment(false)
}
super.onStop()
}
private fun removeEnrollmentObservers() {
preRemoveEnrollmentObservers()
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
}
private fun preRemoveEnrollmentObservers() {
progressViewModel.progressLiveData.removeObserver(progressObserver)
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
}
private fun cancelEnrollment() {
preRemoveEnrollmentObservers()
progressViewModel.cancelEnrollment()
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
if (!progressViewModel.isEnrolling) {
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
return
}
removeEnrollmentObservers()
if (waitForLastCancelErrMsg) {
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
} else {
enrollingCancelSignal = null
}
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
if (!cancelResult) {
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
}
}
private fun startEnrollment() {
val startResult: Boolean =
progressViewModel.startEnrollment(FingerprintManager.ENROLL_ENROLL)
if (!startResult) {
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
if (enrollingCancelSignal == null) {
Log.e(TAG, "startEnrollment(), failed")
} else {
Log.d(TAG, "startEnrollment(), success")
}
progressViewModel.progressLiveData.observe(this, progressObserver)
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
@@ -261,7 +306,7 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
private fun configureEnrollmentStage(description: CharSequence, @RawRes lottie: Int) {
GlifLayoutHelper(requireActivity(), enrollingSfpsView!!).setDescriptionText(description)
GlifLayoutHelper(requireActivity(), enrollingView!!).setDescriptionText(description)
LottieCompositionFactory.fromRawRes(activity, lottie)
.addListener { c: LottieComposition ->
illustrationLottie.setComposition(c)
@@ -290,6 +335,7 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
Log.d(TAG, "onEnrollmentHelp($helpMessage)")
val helpStr: CharSequence = helpMessage.str
if (helpStr.isNotEmpty()) {
showError(helpStr)
@@ -297,25 +343,26 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
removeEnrollmentObservers()
if (enrollingViewModel.onBackPressed
&& errorMessage.msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
) {
enrollingViewModel.onCancelledDueToOnBackPressed()
} else if (enrollingViewModel.onSkipPressed
&& errorMessage.msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
) {
enrollingViewModel.onCancelledDueToOnSkipPressed()
} else {
val errMsgId: Int = errorMessage.msgId
enrollingViewModel.showErrorDialog(
FingerprintEnrollEnrollingViewModel.ErrorDialogData(
getString(FingerprintErrorDialog.getErrorMessage(errMsgId)),
getString(FingerprintErrorDialog.getErrorTitle(errMsgId)),
errMsgId
)
)
progressViewModel.cancelEnrollment()
cancelEnrollment(true)
lifecycleScope.launch {
Log.d(TAG, "newDialog $errorMessage")
errorDialogViewModel.newDialog(errorMessage.msgId)
}
}
private fun onEnrollmentCanceled(canceledSignal: Any) {
Log.d(
TAG,
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
)
if (enrollingCancelSignal === canceledSignal) {
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
progressViewModel.clearProgressLiveData()
if (enrollingViewModel.onBackPressed) {
enrollingViewModel.onCancelledDueToOnBackPressed()
} else if (enrollingViewModel.onSkipPressed) {
enrollingViewModel.onCancelledDueToOnSkipPressed()
}
}
}
@@ -345,6 +392,8 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
val progress = getProgress(enrollmentProgress)
Log.d(TAG, "updateProgress($animate, $enrollmentProgress), old:${progressBar.progress}"
+ ", new:$progress")
// Only clear the error when progress has been made.
// TODO (b/234772728) Add tests.
@@ -365,12 +414,12 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
if (progress.steps == -1) {
return 0
}
val displayProgress = Math.max(0, progress.steps + 1 - progress.remaining)
val displayProgress = 0.coerceAtLeast(progress.steps + 1 - progress.remaining)
return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
}
private fun showError(error: CharSequence) {
enrollingSfpsView!!.let {
enrollingView!!.let {
it.headerText = error
it.headerTextView.contentDescription = error
GlifLayoutHelper(requireActivity(), it).setDescriptionText("")
@@ -425,7 +474,7 @@ class FingerprintEnrollEnrollingSfpsFragment : Fragment() {
}
private fun updateTitleAndDescription() {
val helper = GlifLayoutHelper(requireActivity(), enrollingSfpsView!!)
val helper = GlifLayoutHelper(requireActivity(), enrollingView!!)
if (enrollingViewModel.isAccessibilityEnabled) {
enrollingViewModel.clearTalkback()
helper.glifLayout.descriptionTextView.accessibilityLiveRegion =
@@ -584,7 +633,7 @@ private fun ProgressBar.applyProgressBarDynamicColor(context: Context, isError:
}
fun LottieAnimationView.applyLottieDynamicColor(context: Context, isError: Boolean) {
addValueCallback<ColorFilter>(
addValueCallback(
KeyPath(".blue100", "**"),
LottieProperty.COLOR_FILTER
) {

View File

@@ -17,8 +17,8 @@ package com.android.settings.biometrics2.ui.view
import android.annotation.RawRes
import android.content.Context
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_CANCELED
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Bundle
import android.util.Log
@@ -35,20 +35,25 @@ import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieCompositionFactory
import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
import com.android.settingslib.display.DisplayDensityUtils
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
/**
@@ -68,6 +73,10 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
private val progressViewModel: FingerprintEnrollProgressViewModel
get() = _progressViewModel!!
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
get() = _errorDialogViewModel!!
private var illustrationLottie: LottieAnimationView? = null
private var haveShownTipLottie = false
@@ -76,22 +85,22 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
private var haveShownCenterLottie = false
private var haveShownGuideLottie = false
private var enrollingUdfpsView: RelativeLayout? = null
private var enrollingView: RelativeLayout? = null
private val titleText: TextView
get() = enrollingUdfpsView!!.findViewById<TextView>(R.id.suc_layout_title)!!
get() = enrollingView!!.findViewById(R.id.suc_layout_title)!!
private val subTitleText: TextView
get() = enrollingUdfpsView!!.findViewById<TextView>(R.id.sud_layout_subtitle)!!
get() = enrollingView!!.findViewById(R.id.sud_layout_subtitle)!!
private val udfpsEnrollView: UdfpsEnrollView
get() = enrollingUdfpsView!!.findViewById<UdfpsEnrollView>(R.id.udfps_animation_view)!!
get() = enrollingView!!.findViewById(R.id.udfps_animation_view)!!
private val skipBtn: Button
get() = enrollingUdfpsView!!.findViewById<Button>(R.id.skip_btn)!!
get() = enrollingView!!.findViewById(R.id.skip_btn)!!
private val icon: ImageView
get() = enrollingUdfpsView!!.findViewById<ImageView>(R.id.sud_layout_icon)!!
get() = enrollingView!!.findViewById(R.id.sud_layout_icon)!!
private val shouldShowLottie: Boolean
get() {
@@ -108,24 +117,33 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
private var rotation = -1
private var enrollingCancelSignal: Any? = null
private val onSkipClickListener = View.OnClickListener { _: View? ->
enrollingViewModel.setOnSkipPressed()
cancelEnrollment()
cancelEnrollment(false)
}
private val progressObserver: Observer<EnrollmentProgress> =
Observer<EnrollmentProgress> { progress: EnrollmentProgress? ->
progress?.let { onEnrollmentProgressChange(it) }
private val progressObserver = Observer { progress: EnrollmentProgress? ->
if (progress != null && progress.steps >= 0) {
onEnrollmentProgressChange(progress)
}
}
private val helpMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { helpMessage: EnrollmentStatusMessage? ->
helpMessage?.let { onEnrollmentHelp(it) }
}
private val errorMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
errorMessage?.let { onEnrollmentError(it) }
}
private val helpMessageObserver = Observer { helpMessage: EnrollmentStatusMessage? ->
Log.d(TAG, "helpMessageObserver($helpMessage)")
helpMessage?.let { onEnrollmentHelp(it) }
}
private val errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
Log.d(TAG, "errorMessageObserver($errorMessage)")
errorMessage?.let { onEnrollmentError(it) }
}
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
Log.d(TAG, "canceledSignalObserver($canceledSignal)")
canceledSignal?.let { onEnrollmentCanceled(it) }
}
private val acquireObserver =
Observer { isAcquiredGood: Boolean? -> isAcquiredGood?.let { onAcquired(it) } }
@@ -144,7 +162,7 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
override fun handleOnBackPressed() {
isEnabled = false
enrollingViewModel.setOnBackPressed()
cancelEnrollment()
cancelEnrollment(true)
}
}
@@ -156,6 +174,7 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
_enrollingViewModel = provider[FingerprintEnrollEnrollingViewModel::class.java]
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
}
super.onAttach(context)
requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback)
@@ -172,7 +191,7 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
): View = (inflater.inflate(
R.layout.udfps_enroll_enrolling_v2, container, false
) as RelativeLayout).also {
enrollingUdfpsView = it
enrollingView = it
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -181,23 +200,78 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
updateIllustrationLottie(rotation)
requireActivity().bindFingerprintEnrollEnrollingUdfpsView(
view = enrollingUdfpsView!!,
view = enrollingView!!,
sensorProperties = enrollingViewModel.firstFingerprintSensorPropertiesInternal!!,
rotation = rotation,
onSkipClickListener = onSkipClickListener,
)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.triggerRetryFlow.collect { retryEnrollment() }
}
}
}
private fun retryEnrollment() {
reattachUdfpsEnrollView()
startEnrollment()
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
progressViewModel.helpMessageLiveData.value.let {
if (it != null) {
onEnrollmentHelp(it)
} else {
updateTitleAndDescription()
}
}
}
override fun onStart() {
super.onStart()
startEnrollment()
val isEnrolling = progressViewModel.isEnrolling
val isErrorDialogShown = errorDialogViewModel.isDialogShown
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling, isErrorDialog:$isErrorDialogShown")
if (!isErrorDialogShown) {
startEnrollment()
}
updateProgress(false /* animate */, progressViewModel.progressLiveData.value!!)
val msg: EnrollmentStatusMessage? = progressViewModel.helpMessageLiveData.value
if (msg != null) {
onEnrollmentHelp(msg)
} else {
updateTitleAndDescription()
progressViewModel.helpMessageLiveData.value.let {
if (it != null) {
onEnrollmentHelp(it)
} else {
updateTitleAndDescription()
}
}
}
private fun reattachUdfpsEnrollView() {
enrollingView!!.let {
val newUdfpsView = LayoutInflater.from(requireActivity()).inflate(
R.layout.udfps_enroll_enrolling_v2_udfps_view,
null
)
val index = it.indexOfChild(udfpsEnrollView)
val lp = udfpsEnrollView.layoutParams
it.removeView(udfpsEnrollView)
it.addView(newUdfpsView, index, lp)
udfpsEnrollView.setSensorProperties(
enrollingViewModel.firstFingerprintSensorPropertiesInternal
)
}
// Clear lottie status
haveShownTipLottie = false
haveShownLeftEdgeLottie = false
haveShownRightEdgeLottie = false
haveShownCenterLottie = false
haveShownGuideLottie = false
illustrationLottie?.let {
it.contentDescription = ""
it.visibility = View.GONE
}
}
@@ -213,18 +287,17 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
override fun onStop() {
removeEnrollmentObservers()
if (!activity!!.isChangingConfigurations && progressViewModel.isEnrolling) {
progressViewModel.cancelEnrollment()
val isEnrolling = progressViewModel.isEnrolling
val isConfigChange = requireActivity().isChangingConfigurations
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
if (isEnrolling && !isConfigChange) {
cancelEnrollment(false)
}
super.onStop()
}
private fun removeEnrollmentObservers() {
preRemoveEnrollmentObservers()
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
}
private fun preRemoveEnrollmentObservers() {
progressViewModel.progressLiveData.removeObserver(progressObserver)
progressViewModel.helpMessageLiveData.removeObserver(helpMessageObserver)
progressViewModel.acquireLiveData.removeObserver(acquireObserver)
@@ -232,16 +305,29 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
progressViewModel.pointerUpLiveData.removeObserver(pointerUpObserver)
}
private fun cancelEnrollment() {
preRemoveEnrollmentObservers()
progressViewModel.cancelEnrollment()
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
if (!progressViewModel.isEnrolling) {
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
return
}
removeEnrollmentObservers()
if (waitForLastCancelErrMsg) {
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
} else {
enrollingCancelSignal = null
}
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
if (!cancelResult) {
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
}
}
private fun startEnrollment() {
val startResult: Boolean =
progressViewModel.startEnrollment(ENROLL_ENROLL)
if (!startResult) {
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_ENROLL)
if (enrollingCancelSignal == null) {
Log.e(TAG, "startEnrollment(), failed")
} else {
Log.d(TAG, "startEnrollment(), success")
}
progressViewModel.progressLiveData.observe(this, progressObserver)
progressViewModel.helpMessageLiveData.observe(this, helpMessageObserver)
@@ -256,17 +342,24 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
Log.d(TAG, "Enrollment not started yet")
return
}
val progress = getProgress(enrollmentProgress)
if (progressViewModel.progressLiveData.value!!.steps != -1) {
Log.d(TAG, "updateProgress($animate, $enrollmentProgress), progress:$progress")
if (enrollmentProgress.steps != -1) {
udfpsEnrollView.onEnrollmentProgress(
enrollmentProgress.remaining,
enrollmentProgress.steps
)
}
if (animate) {
animateProgress(progress)
} else if (progress >= PROGRESS_BAR_MAX) {
delayedFinishRunnable.run()
if (progress >= PROGRESS_BAR_MAX) {
if (animate) {
// Wait animations to finish, then proceed to next page
activity!!.mainThreadHandler.postDelayed(delayedFinishRunnable, 400L)
} else {
delayedFinishRunnable.run()
}
}
}
@@ -278,15 +371,8 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
return PROGRESS_BAR_MAX * displayProgress / (progress.steps + 1)
}
private fun animateProgress(progress: Int) {
// UDFPS animations are owned by SystemUI
if (progress >= PROGRESS_BAR_MAX) {
// Wait for any animations in SysUI to finish, then proceed to next page
activity!!.mainThreadHandler.postDelayed(delayedFinishRunnable, 400L)
}
}
private fun updateTitleAndDescription() {
Log.d(TAG, "updateTitleAndDescription($currentStage)")
when (currentStage) {
STAGE_CENTER -> {
titleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title)
@@ -386,7 +472,7 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
illustrationLottie = null
} else if (shouldShowLottie) {
illustrationLottie =
enrollingUdfpsView!!.findViewById<LottieAnimationView>(R.id.illustration_lottie)
enrollingView!!.findViewById(R.id.illustration_lottie)
}
}
@@ -468,6 +554,7 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
}
private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
Log.d(TAG, "onEnrollmentHelp($helpMessage)")
val helpStr: CharSequence = helpMessage.str
if (helpStr.isNotEmpty()) {
showError(helpStr)
@@ -476,25 +563,26 @@ class FingerprintEnrollEnrollingUdfpsFragment : Fragment() {
}
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
removeEnrollmentObservers()
if (enrollingViewModel.onBackPressed
&& errorMessage.msgId == FINGERPRINT_ERROR_CANCELED
) {
enrollingViewModel.onCancelledDueToOnBackPressed()
} else if (enrollingViewModel.onSkipPressed
&& errorMessage.msgId == FINGERPRINT_ERROR_CANCELED
) {
enrollingViewModel.onCancelledDueToOnSkipPressed()
} else {
val errMsgId: Int = errorMessage.msgId
enrollingViewModel.showErrorDialog(
FingerprintEnrollEnrollingViewModel.ErrorDialogData(
getString(FingerprintErrorDialog.getErrorMessage(errMsgId)),
getString(FingerprintErrorDialog.getErrorTitle(errMsgId)),
errMsgId
)
)
progressViewModel.cancelEnrollment()
cancelEnrollment(true)
lifecycleScope.launch {
Log.d(TAG, "newDialog $errorMessage")
errorDialogViewModel.newDialog(errorMessage.msgId)
}
}
private fun onEnrollmentCanceled(canceledSignal: Any) {
Log.d(
TAG,
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
)
if (enrollingCancelSignal === canceledSignal) {
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
progressViewModel.clearProgressLiveData()
if (enrollingViewModel.onBackPressed) {
enrollingViewModel.onCancelledDueToOnBackPressed()
} else if (enrollingViewModel.onSkipPressed) {
enrollingViewModel.onCancelledDueToOnSkipPressed()
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2023 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.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog.getErrorMessage
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog.getErrorTitle
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog.getSetupErrorMessage
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
import kotlinx.coroutines.launch
/**
* Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment.
*/
class FingerprintEnrollErrorDialog : DialogFragment() {
private val viewModel: FingerprintEnrollErrorDialogViewModel?
get() = activity?.let {
ViewModelProvider(it)[FingerprintEnrollErrorDialogViewModel::class.java]
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val errorMsgId: Int = requireArguments().getInt(KEY_ERROR_MSG_ID)
val okButtonSetResultAction =
if (errorMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT)
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
else
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
return requireActivity().bindFingerprintEnrollEnrollingErrorDialog(
errorMsgId = errorMsgId,
isSuw = viewModel!!.isSuw,
tryAgainButtonClickListener = { dialog: DialogInterface?, _: Int ->
activity?.lifecycleScope?.launch {
Log.d(TAG, "tryAgain flow")
viewModel?.triggerRetry()
dialog?.dismiss()
}
},
okButtonClickListener = { dialog: DialogInterface?, _: Int ->
activity?.lifecycleScope?.launch {
Log.d(TAG, "ok flow as $okButtonSetResultAction")
viewModel?.setResultAndFinish(okButtonSetResultAction)
dialog?.dismiss()
}
}
)
}
companion object {
private const val TAG = "FingerprintEnrollErrorDialog"
private const val KEY_ERROR_MSG_ID = "error_msg_id"
fun newInstance(errorMsgId: Int): FingerprintEnrollErrorDialog {
val dialog = FingerprintEnrollErrorDialog()
val args = Bundle()
args.putInt(KEY_ERROR_MSG_ID, errorMsgId)
dialog.arguments = args
return dialog
}
}
}
fun Context.bindFingerprintEnrollEnrollingErrorDialog(
errorMsgId: Int,
isSuw: Boolean,
tryAgainButtonClickListener: DialogInterface.OnClickListener,
okButtonClickListener: DialogInterface.OnClickListener
): AlertDialog = AlertDialog.Builder(this)
.setTitle(getString(getErrorTitle(errorMsgId)))
.setMessage(
getString(
if (isSuw)
getSetupErrorMessage(errorMsgId)
else
getErrorMessage(errorMsgId)
)
)
.setCancelable(false).apply {
if (errorMsgId == FINGERPRINT_ERROR_UNABLE_TO_PROCESS) {
setPositiveButton(
R.string.security_settings_fingerprint_enroll_dialog_try_again,
tryAgainButtonClickListener
)
setNegativeButton(
R.string.security_settings_fingerprint_enroll_dialog_ok,
okButtonClickListener
)
} else {
setPositiveButton(
R.string.security_settings_fingerprint_enroll_dialog_ok,
okButtonClickListener
)
}
}
.create()
.apply { setCanceledOnTouchOutside(false) }

View File

@@ -16,7 +16,7 @@
package com.android.settings.biometrics2.ui.view
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR
import android.os.Bundle
import android.util.Log
@@ -26,19 +26,25 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
import com.android.settings.biometrics2.ui.model.EnrollmentProgress
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.launch
/**
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
@@ -68,6 +74,10 @@ class FingerprintEnrollFindRfpsFragment : Fragment() {
private val rotationViewModel: DeviceRotationViewModel
get() = _rotationViewModel!!
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
get() = _errorDialogViewModel!!
private var findRfpsView: GlifLayout? = null
private val onSkipClickListener =
@@ -75,33 +85,25 @@ class FingerprintEnrollFindRfpsFragment : Fragment() {
private var animation: FingerprintFindSensorAnimation? = null
private var enrollingCancelSignal: Any? = null
@Surface.Rotation
private var lastRotation = -1
private val rotationObserver = Observer { rotation: Int? ->
if (DEBUG) {
Log.d(TAG, "rotationObserver $rotation")
private val progressObserver = Observer { progress: EnrollmentProgress? ->
if (progress != null && !progress.isInitialStep) {
cancelEnrollment(true)
}
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 errorMessageObserver = Observer { errorMessage: EnrollmentStatusMessage? ->
Log.d(TAG, "errorMessageObserver($errorMessage)")
errorMessage?.let { onEnrollmentError(it) }
}
private val lastCancelMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "lastCancelMessageObserver($errorMessage)")
}
errorMessage?.let { onLastCancelMessage(it) }
}
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
canceledSignal?.let { onEnrollmentCanceled(it) }
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -129,28 +131,40 @@ class FingerprintEnrollFindRfpsFragment : Fragment() {
view = findRfpsView!!,
onSkipClickListener = onSkipClickListener
)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.triggerRetryFlow.collect { retryLookingForFingerprint() }
}
}
}
private fun retryLookingForFingerprint() {
startEnrollment()
animation?.let {
Log.d(TAG, "retry, start animation")
it.startAnimation()
}
}
override fun onStart() {
super.onStart()
if (DEBUG) {
Log.d(
TAG,
"onStart(), start looking for fingerprint, animation exist:${animation != null}"
)
val isErrorDialogShown = errorDialogViewModel.isDialogShown
Log.d(TAG, "onStart(), isEnrolling:${progressViewModel.isEnrolling}"
+ ", isErrorDialog:$isErrorDialogShown")
if (!isErrorDialogShown) {
startEnrollment()
}
startLookingForFingerprint()
}
override fun onResume() {
val rotationLiveData: LiveData<Int> = rotationViewModel.liveData
lastRotation = rotationLiveData.value!!
rotationLiveData.observe(this, rotationObserver)
animation?.let {
if (DEBUG) {
if (!errorDialogViewModel.isDialogShown) {
animation?.let {
Log.d(TAG, "onResume(), start animation")
it.startAnimation()
}
it.startAnimation()
}
super.onResume()
}
@@ -167,72 +181,68 @@ class FingerprintEnrollFindRfpsFragment : Fragment() {
override fun onStop() {
super.onStop()
val isEnrolling: Boolean = progressViewModel.isEnrolling
if (DEBUG) {
Log.d(
TAG,
"onStop(), current enrolling: ${isEnrolling}, animation exist:${animation != null}"
)
}
if (isEnrolling) {
stopLookingForFingerprint(false)
removeEnrollmentObservers()
val isEnrolling = progressViewModel.isEnrolling
val isConfigChange = requireActivity().isChangingConfigurations
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
if (isEnrolling && !isConfigChange) {
cancelEnrollment(false)
}
}
private fun startLookingForFingerprint() {
if (progressViewModel.isEnrolling) {
Log.d(
TAG,
"startLookingForFingerprint(), failed because isEnrolling is true before starting"
)
return
}
val startResult: Boolean = progressViewModel.startEnrollment(ENROLL_FIND_SENSOR)
if (!startResult) {
Log.e(TAG, "startLookingForFingerprint(), failed to start enrollment")
private fun removeEnrollmentObservers() {
progressViewModel.progressLiveData.removeObserver(progressObserver)
progressViewModel.helpMessageLiveData.removeObserver(errorMessageObserver)
}
private fun startEnrollment() {
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_FIND_SENSOR)
if (enrollingCancelSignal == null) {
Log.e(TAG, "startEnrollment(), failed to start enrollment")
} else {
Log.d(TAG, "startEnrollment(), success")
}
progressViewModel.progressLiveData.observe(this, progressObserver)
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
}
private fun stopLookingForFingerprint(waitForLastCancelErrMsg: Boolean) {
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
if (!progressViewModel.isEnrolling) {
Log.d(
TAG,
"stopLookingForFingerprint(), failed because isEnrolling is false before stopping"
)
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
return
}
removeEnrollmentObservers()
if (waitForLastCancelErrMsg) {
progressViewModel.clearErrorMessageLiveData() // Prevent got previous error message
progressViewModel.errorMessageLiveData.observe(this, lastCancelMessageObserver)
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
} else {
enrollingCancelSignal = null
}
progressViewModel.progressLiveData.removeObserver(progressObserver)
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
if (!cancelResult) {
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment")
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
}
}
private fun onRotationChanged(@Surface.Rotation newRotation: Int) {
if (DEBUG) {
Log.d(TAG, "onRotationChanged() from $lastRotation to $newRotation")
}
if (newRotation % 2 != lastRotation % 2) {
// Fragment is going to be recreated, just stopLookingForFingerprint() here.
stopLookingForFingerprint(true)
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
cancelEnrollment(false)
lifecycleScope.launch {
Log.d(TAG, "newDialogFlow as $errorMessage")
errorDialogViewModel.newDialog(errorMessage.msgId)
}
}
private fun onLastCancelMessage(errorMessage: EnrollmentStatusMessage) {
if (errorMessage.msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
private fun onEnrollmentCanceled(canceledSignal: Any) {
Log.d(
TAG,
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
)
if (enrollingCancelSignal === canceledSignal) {
val progress: EnrollmentProgress? = progressViewModel.progressLiveData.value
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
progressViewModel.clearProgressLiveData()
progressViewModel.errorMessageLiveData.removeObserver(lastCancelMessageObserver)
if (progress != null && !progress.isInitialStep) {
viewModel.onStartButtonClick()
}
} else {
Log.e(TAG, "errorMessageObserver($errorMessage)")
}
}
@@ -251,6 +261,7 @@ class FingerprintEnrollFindRfpsFragment : Fragment() {
_viewModel = provider[FingerprintEnrollFindSensorViewModel::class.java]
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
}
super.onAttach(context)
}

View File

@@ -16,7 +16,8 @@
package com.android.settings.biometrics2.ui.view
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.fingerprint.FingerprintManager.ENROLL_FIND_SENSOR
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@@ -26,21 +27,27 @@ import android.view.ViewGroup
import androidx.annotation.RawRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
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.FingerprintEnrollErrorDialogViewModel
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
import kotlinx.coroutines.launch
/**
* Fragment explaining the side fingerprint sensor location for fingerprint enrollment.
@@ -75,41 +82,41 @@ class FingerprintEnrollFindSfpsFragment : Fragment() {
private val foldedViewModel: DeviceFoldedViewModel
get() = _foldedViewModel!!
private var _errorDialogViewModel: FingerprintEnrollErrorDialogViewModel? = null
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel
get() = _errorDialogViewModel!!
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)!!
get() = findSfpsView!!.findViewById(R.id.illustration_lottie)!!
private var enrollingCancelSignal: Any? = null
@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 progressObserver = Observer { progress: EnrollmentProgress? ->
if (progress != null && !progress.isInitialStep) {
cancelEnrollment(true)
}
}
private val lastCancelMessageObserver: Observer<EnrollmentStatusMessage> =
Observer<EnrollmentStatusMessage> { errorMessage: EnrollmentStatusMessage? ->
if (DEBUG) {
Log.d(TAG, "lastCancelMessageObserver($errorMessage)")
}
errorMessage?.let { onLastCancelMessage(it) }
}
private val errorMessageObserver = Observer{ errorMessage: EnrollmentStatusMessage? ->
Log.d(TAG, "errorMessageObserver($errorMessage)")
errorMessage?.let { onEnrollmentError(it) }
}
private val canceledSignalObserver = Observer { canceledSignal: Any? ->
canceledSignal?.let { onEnrollmentCanceled(it) }
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -128,16 +135,21 @@ class FingerprintEnrollFindSfpsFragment : Fragment() {
view = findSfpsView!!,
onSkipClickListener = onSkipClickListener
)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.triggerRetryFlow.collect { startEnrollment() }
}
}
}
override fun onStart() {
super.onStart()
val isEnrolling: Boolean = progressViewModel.isEnrolling
if (DEBUG) {
Log.d(TAG, "onStart(), isEnrolling:$isEnrolling")
}
if (!isEnrolling) {
startLookingForFingerprint()
val isErrorDialogShown = errorDialogViewModel.isDialogShown
Log.d(TAG, "onStart(), isEnrolling:${progressViewModel.isEnrolling}"
+ ", isErrorDialog:$isErrorDialogShown")
if (!isErrorDialogShown) {
startEnrollment()
}
}
@@ -155,51 +167,44 @@ class FingerprintEnrollFindSfpsFragment : Fragment() {
override fun onStop() {
super.onStop()
val isEnrolling: Boolean = progressViewModel.isEnrolling
if (DEBUG) {
Log.d(TAG, "onStop(), isEnrolling:$isEnrolling")
}
if (isEnrolling) {
stopLookingForFingerprint(false)
val isEnrolling = progressViewModel.isEnrolling
val isConfigChange = requireActivity().isChangingConfigurations
Log.d(TAG, "onStop(), enrolling:$isEnrolling isConfigChange:$isConfigChange")
if (isEnrolling && !isConfigChange) {
cancelEnrollment(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
)
}
private fun removeEnrollmentObservers() {
progressViewModel.errorMessageLiveData.removeObserver(errorMessageObserver)
progressViewModel.progressLiveData.removeObserver(progressObserver)
}
private fun startEnrollment() {
enrollingCancelSignal = progressViewModel.startEnrollment(ENROLL_FIND_SENSOR)
if (enrollingCancelSignal == null) {
Log.e(TAG, "startEnrollment(), failed to start enrollment")
} else {
Log.d(TAG, "startEnrollment(), success")
}
progressViewModel.progressLiveData.observe(this, progressObserver)
progressViewModel.errorMessageLiveData.observe(this, errorMessageObserver)
}
private fun cancelEnrollment(waitForLastCancelErrMsg: Boolean) {
if (!progressViewModel.isEnrolling) {
Log.d(TAG, "cancelEnrollment(), failed because isEnrolling is false")
return
}
removeEnrollmentObservers()
if (waitForLastCancelErrMsg) {
progressViewModel.canceledSignalLiveData.observe(this, canceledSignalObserver)
} else {
enrollingCancelSignal = null
}
val cancelResult: Boolean = progressViewModel.cancelEnrollment()
if (!cancelResult) {
Log.e(TAG, "stopLookingForFingerprint(), failed to cancel enrollment")
Log.e(TAG, "cancelEnrollment(), failed to cancel enrollment")
}
}
@@ -210,34 +215,39 @@ class FingerprintEnrollFindSfpsFragment : Fragment() {
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) {
private fun onEnrollmentError(errorMessage: EnrollmentStatusMessage) {
progressViewModel.cancelEnrollment()
lifecycleScope.launch {
Log.d(TAG, "newDialogFlow as $errorMessage")
errorDialogViewModel.newDialog(errorMessage.msgId)
}
}
private fun onEnrollmentCanceled(canceledSignal: Any) {
Log.d(
TAG,
"onEnrollmentCanceled enrolling:$enrollingCancelSignal, canceled:$canceledSignal"
)
if (enrollingCancelSignal === canceledSignal) {
val progress: EnrollmentProgress? = progressViewModel.progressLiveData.value
progressViewModel.canceledSignalLiveData.removeObserver(canceledSignalObserver)
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
)
}
Log.d(
TAG,
"play lottie animation $animationRawRes, previous rotation:$animationRotation"
+ ", new rotation:" + rotation
)
animationRotation = rotation
illustrationLottie.setAnimation(animationRawRes)
LottieColorUtils.applyDynamicColors(activity, illustrationLottie)
@@ -247,7 +257,7 @@ class FingerprintEnrollFindSfpsFragment : Fragment() {
@RawRes
private fun getSfpsLottieAnimationRawRes(@Surface.Rotation rotation: Int): Int {
val isFolded = java.lang.Boolean.FALSE != foldedViewModel.getLiveData().getValue()
val isFolded = java.lang.Boolean.FALSE != foldedViewModel.liveData.value
return when (rotation) {
Surface.ROTATION_90 ->
if (isFolded)
@@ -278,6 +288,7 @@ class FingerprintEnrollFindSfpsFragment : Fragment() {
_progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
_rotationViewModel = provider[DeviceRotationViewModel::class.java]
_foldedViewModel = provider[DeviceFoldedViewModel::class.java]
_errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
}
super.onAttach(context)
}

View File

@@ -32,8 +32,11 @@ import androidx.annotation.ColorInt
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import com.android.settings.R
@@ -53,15 +56,12 @@ import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CRE
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.ErrorDialogData
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingAction
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintErrorDialogAction
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollErrorDialogViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_DIALOG
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFindSensorViewModel.FINGERPRINT_ENROLL_FIND_SENSOR_ACTION_SKIP
@@ -78,8 +78,11 @@ import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewM
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FingerprintEnrollIntroAction
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.google.android.setupdesign.util.ThemeHelper
import kotlinx.coroutines.launch
/**
* Fingerprint enrollment activity implementation
@@ -103,6 +106,30 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
viewModelProvider[AutoCredentialViewModel::class.java]
}
private val introViewModel: FingerprintEnrollIntroViewModel by lazy {
viewModelProvider[FingerprintEnrollIntroViewModel::class.java]
}
private val findSensorViewModel: FingerprintEnrollFindSensorViewModel by lazy {
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
}
private val progressViewModel: FingerprintEnrollProgressViewModel by lazy {
viewModelProvider[FingerprintEnrollProgressViewModel::class.java]
}
private val enrollingViewModel: FingerprintEnrollEnrollingViewModel by lazy {
viewModelProvider[FingerprintEnrollEnrollingViewModel::class.java]
}
private val finishViewModel: FingerprintEnrollFinishViewModel by lazy {
viewModelProvider[FingerprintEnrollFinishViewModel::class.java]
}
private val errorDialogViewModel: FingerprintEnrollErrorDialogViewModel by lazy {
viewModelProvider[FingerprintEnrollErrorDialogViewModel::class.java]
}
private val introActionObserver: Observer<Int> = Observer<Int> { action ->
if (DEBUG) {
Log.d(TAG, "introActionObserver($action)")
@@ -124,26 +151,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
action?.let { onEnrollingAction(it) }
}
private val enrollingErrorDialogObserver: Observer<ErrorDialogData> =
Observer<ErrorDialogData> { data ->
if (DEBUG) {
Log.d(TAG, "enrollingErrorDialogObserver($data)")
}
data?.let {
FingerprintEnrollEnrollingErrorDialog().show(
supportFragmentManager,
ENROLLING_ERROR_DIALOG_TAG
)
}
}
private val enrollingErrorDialogActionObserver: Observer<Int> = Observer<Int> { action ->
if (DEBUG) {
Log.d(TAG, "enrollingErrorDialogActionObserver($action)")
}
action?.let { onEnrollingErrorDialogAction(it) }
}
private val finishActionObserver: Observer<Int> = Observer<Int> { action ->
if (DEBUG) {
Log.d(TAG, "finishActionObserver($action)")
@@ -218,6 +225,33 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
autoCredentialViewModel.generateChallengeFailedLiveData.observe(this) {
_: Boolean -> onGenerateChallengeFailed()
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.newDialogFlow.collect {
Log.d(TAG, "newErrorDialogFlow($it)")
FingerprintEnrollErrorDialog.newInstance(it).show(
supportFragmentManager,
ERROR_DIALOG_TAG
)
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.setResultFlow.collect {
Log.d(TAG, "errorDialogSetResultFlow($it)")
when (it) {
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH -> onSetActivityResult(
ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null)
)
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT -> onSetActivityResult(
ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)
)
}
}
}
}
}
private fun startFragment(fragmentClass: Class<out Fragment>, tag: String) {
@@ -252,7 +286,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
if (request.isSkipIntro || request.isSkipFindSensor) {
return
}
viewModelProvider[FingerprintEnrollIntroViewModel::class.java].let {
introViewModel.let {
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
// recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor
// activity.
@@ -264,8 +298,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
// We need to make sure token is valid before entering find sensor page
private fun startFindSensorFragment() {
// Always setToken into progressViewModel even it is not necessary action for UDFPS
viewModelProvider[FingerprintEnrollProgressViewModel::class.java]
.setToken(autoCredentialViewModel.token)
progressViewModel.setToken(autoCredentialViewModel.token)
attachFindSensorViewModel()
val fragmentClass: Class<out Fragment> = if (viewModel.canAssumeUdfps()) {
FingerprintEnrollFindUdfpsFragment::class.java
@@ -281,7 +314,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
if (viewModel.request.isSkipFindSensor) {
return
}
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java].let {
findSensorViewModel.let {
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
// recreate, like press 'Start' then press 'back' in FingerprintEnrollEnrolling
// activity.
@@ -292,8 +325,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
private fun startEnrollingFragment() {
// Always setToken into progressViewModel even it is not necessary action for SFPS or RFPS
viewModelProvider[FingerprintEnrollProgressViewModel::class.java]
.setToken(autoCredentialViewModel.token)
progressViewModel.setToken(autoCredentialViewModel.token)
attachEnrollingViewModel()
val fragmentClass: Class<out Fragment> = if (viewModel.canAssumeUdfps()) {
FingerprintEnrollEnrollingUdfpsFragment::class.java
@@ -306,14 +338,9 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
}
private fun attachEnrollingViewModel() {
viewModelProvider[FingerprintEnrollEnrollingViewModel::class.java].let {
enrollingViewModel.let {
it.clearActionLiveData()
it.actionLiveData.observe(this, enrollingActionObserver)
it.errorDialogLiveData.observe(this, enrollingErrorDialogObserver)
it.errorDialogActionLiveData.observe(
this,
enrollingErrorDialogActionObserver
)
}
}
@@ -374,7 +401,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
}
private fun attachFinishViewModel() {
viewModelProvider[FingerprintEnrollFinishViewModel::class.java].let {
finishViewModel.let {
it.clearActionLiveData()
it.actionLiveData.observe(this, finishActionObserver)
}
@@ -520,18 +547,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
}
}
private fun onEnrollingErrorDialogAction(@FingerprintErrorDialogAction action: Int) {
when (action) {
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH -> onSetActivityResult(
ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null)
)
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT -> onSetActivityResult(
ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)
)
}
}
private fun onFinishAction(@FingerprintEnrollFinishAction action: Int) {
when (action) {
FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK -> {
@@ -623,12 +638,13 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
companion object {
private const val DEBUG = false
private const val TAG = "FingerprintEnrollmentActivity"
protected const val LAUNCH_CONFIRM_LOCK_ACTIVITY = 1
private const val INTRO_TAG = "intro"
private const val FIND_SENSOR_TAG = "find-sensor"
private const val ENROLLING_TAG = "enrolling"
private const val FINISH_TAG = "finish"
private const val SKIP_SETUP_FIND_FPS_DIALOG_TAG = "skip-setup-dialog"
private const val ENROLLING_ERROR_DIALOG_TAG = "enrolling-error-dialog"
protected const val LAUNCH_CONFIRM_LOCK_ACTIVITY = 1
private const val ERROR_DIALOG_TAG = "error-dialog"
}
}

View File

@@ -59,9 +59,7 @@ public class DeviceRotationViewModel extends AndroidViewModel {
@Override
public void onDisplayChanged(int displayId) {
final int rotation = getRotation();
if (DEBUG) {
Log.d(TAG, "onDisplayChanged(" + displayId + "), rotation:" + rotation);
}
Log.d(TAG, "onDisplayChanged(" + displayId + "), rotation:" + rotation);
mLiveData.postValue(rotation);
}
};
@@ -98,10 +96,11 @@ public class DeviceRotationViewModel extends AndroidViewModel {
* Returns RotationLiveData
*/
public LiveData<Integer> getLiveData() {
if (mLiveData.getValue() == null) {
// Init data here because if we set it through getDisplay().getRotation() or through
// getDisplay().getDisplayInfo() in constructor(), we always get incorrect value.
mLiveData.setValue(getRotation());
final Integer lastRotation = mLiveData.getValue();
@Surface.Rotation int newRotation = getRotation();
if (lastRotation == null || lastRotation != newRotation) {
Log.d(TAG, "getLiveData, update rotation from " + lastRotation + " to " + newRotation);
mLiveData.setValue(newRotation);
}
return mLiveData;
}

View File

@@ -73,29 +73,11 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
@IntDef(prefix = { "FINGERPRINT_ENROLL_ENROLLING_ACTION_" }, value = {
FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE,
FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG,
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP,
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED
FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP
})
@Retention(RetentionPolicy.SOURCE)
public @interface FingerprintEnrollEnrollingAction {}
/**
* Enrolling skipped
*/
public static final int FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH = 0;
/**
* Enrolling finished
*/
public static final int FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT = 1;
@IntDef(prefix = { "FINGERPRINT_ERROR_DIALOG_ACTION_" }, value = {
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH,
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
})
@Retention(RetentionPolicy.SOURCE)
public @interface FingerprintErrorDialogAction {}
private final int mUserId;
private boolean mOnBackPressed;
private boolean mOnSkipPressed;
@@ -104,11 +86,12 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
private final Vibrator mVibrator;
private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
private final MutableLiveData<ErrorDialogData> mErrorDialogLiveData = new MutableLiveData<>();
private final MutableLiveData<Integer> mErrorDialogActionLiveData = new MutableLiveData<>();
public FingerprintEnrollEnrollingViewModel(@NonNull Application application,
int userId, @NonNull FingerprintRepository fingerprintRepository) {
public FingerprintEnrollEnrollingViewModel(
@NonNull Application application,
int userId,
@NonNull FingerprintRepository fingerprintRepository
) {
super(application);
mUserId = userId;
mFingerprintRepository = fingerprintRepository;
@@ -116,21 +99,6 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
mVibrator = application.getSystemService(Vibrator.class);
}
/**
* Notifies activity to show error dialog
*/
public void showErrorDialog(@NonNull ErrorDialogData errorDialogData) {
mErrorDialogLiveData.postValue(errorDialogData);
}
public LiveData<ErrorDialogData> getErrorDialogLiveData() {
return mErrorDialogLiveData;
}
public LiveData<Integer> getErrorDialogActionLiveData() {
return mErrorDialogActionLiveData;
}
public LiveData<Integer> getActionLiveData() {
return mActionLiveData;
}
@@ -142,16 +110,6 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
mActionLiveData.setValue(null);
}
/**
* Saves new user dialog action to mErrorDialogActionLiveData
*/
public void onErrorDialogAction(@FingerprintErrorDialogAction int action) {
if (DEBUG) {
Log.d(TAG, "onErrorDialogAction(" + action + ")");
}
mErrorDialogActionLiveData.postValue(action);
}
public boolean getOnSkipPressed() {
return mOnSkipPressed;
}
@@ -164,7 +122,7 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
}
/**
* Enrolling is cacelled because user clicks skip
* Enrolling is cancelled because user clicks skip
*/
public void onCancelledDueToOnSkipPressed() {
final int action = FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP;
@@ -287,38 +245,4 @@ public class FingerprintEnrollEnrollingViewModel extends AndroidViewModel {
public FingerprintSensorPropertiesInternal getFirstFingerprintSensorPropertiesInternal() {
return mFingerprintRepository.getFirstFingerprintSensorPropertiesInternal();
}
/**
* Data for passing to FingerprintEnrollEnrollingErrorDialog
*/
public static class ErrorDialogData {
@NonNull private final CharSequence mErrMsg;
@NonNull private final CharSequence mErrTitle;
@NonNull private final int mErrMsgId;
public ErrorDialogData(@NonNull CharSequence errMsg, @NonNull CharSequence errTitle,
int errMsgId) {
mErrMsg = errMsg;
mErrTitle = errTitle;
mErrMsgId = errMsgId;
}
public CharSequence getErrMsg() {
return mErrMsg;
}
public CharSequence getErrTitle() {
return mErrTitle;
}
public int getErrMsgId() {
return mErrMsgId;
}
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())
+ "{id:" + mErrMsgId + "}";
}
}
}

View File

@@ -0,0 +1,51 @@
package com.android.settings.biometrics2.ui.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class FingerprintEnrollErrorDialogViewModel(
application: Application,
val isSuw: Boolean
): AndroidViewModel(application) {
private val _isDialogShown: AtomicBoolean = atomic(false)
val isDialogShown: Boolean
get() = _isDialogShown.value
private val _newDialogFlow = MutableSharedFlow<Int>()
val newDialogFlow: SharedFlow<Int>
get() = _newDialogFlow.asSharedFlow()
private val _triggerRetryFlow = MutableSharedFlow<Any>()
val triggerRetryFlow: SharedFlow<Any>
get() = _triggerRetryFlow.asSharedFlow()
private val _setResultFlow = MutableSharedFlow<FingerprintErrorDialogSetResultAction>()
val setResultFlow: SharedFlow<FingerprintErrorDialogSetResultAction>
get() = _setResultFlow.asSharedFlow()
suspend fun newDialog(errorMsgId: Int) {
_isDialogShown.compareAndSet(expect = false, update = true)
_newDialogFlow.emit(errorMsgId)
}
suspend fun triggerRetry() {
_isDialogShown.compareAndSet(expect = true, update = false)
_triggerRetryFlow.emit(Any())
}
suspend fun setResultAndFinish(action: FingerprintErrorDialogSetResultAction) {
_isDialogShown.compareAndSet(expect = true, update = false)
_setResultFlow.emit(action)
}
}
enum class FingerprintErrorDialogSetResultAction {
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH,
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.biometrics2.ui.viewmodel;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED;
import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING;
@@ -41,6 +42,8 @@ import com.android.settings.biometrics.fingerprint.MessageDisplayController;
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
import java.util.LinkedList;
/**
* Progress ViewModel handles the state around biometric enrollment. It manages the state of
* enrollment throughout the activity lifecycle so the app can continue after an event like
@@ -57,6 +60,7 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
new MutableLiveData<>();
private final MutableLiveData<EnrollmentStatusMessage> mErrorMessageLiveData =
new MutableLiveData<>();
private final MutableLiveData<Object> mCanceledSignalLiveData = new MutableLiveData<>();
private final MutableLiveData<Boolean> mAcquireLiveData = new MutableLiveData<>();
private final MutableLiveData<Integer> mPointerDownLiveData = new MutableLiveData<>();
private final MutableLiveData<Integer> mPointerUpLiveData = new MutableLiveData<>();
@@ -66,6 +70,8 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
private final FingerprintUpdater mFingerprintUpdater;
@Nullable private CancellationSignal mCancellationSignal = null;
@NonNull private final LinkedList<CancellationSignal> mCancelingSignalQueue =
new LinkedList<>();
private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() {
@Override
@@ -91,10 +97,13 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
if (DEBUG) {
Log.d(TAG, "onEnrollmentError(" + errMsgId + ", " + errString + ")");
Log.d(TAG, "onEnrollmentError(" + errMsgId + ", " + errString
+ "), cancelingQueueSize:" + mCancelingSignalQueue.size());
if (FINGERPRINT_ERROR_CANCELED == errMsgId && mCancelingSignalQueue.size() > 0) {
mCanceledSignalLiveData.postValue(mCancelingSignalQueue.poll());
} else {
mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString));
}
mErrorMessageLiveData.postValue(new EnrollmentStatusMessage(errMsgId, errString));
}
@Override
@@ -152,6 +161,10 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
return mErrorMessageLiveData;
}
public LiveData<Object> getCanceledSignalLiveData() {
return mCanceledSignalLiveData;
}
public LiveData<Boolean> getAcquireLiveData() {
return mAcquireLiveData;
}
@@ -167,14 +180,14 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
/**
* Starts enrollment and return latest isEnrolling() result
*/
public boolean startEnrollment(@EnrollReason int reason) {
public Object startEnrollment(@EnrollReason int reason) {
if (mToken == null) {
Log.e(TAG, "Null hardware auth token for enroll");
return false;
return null;
}
if (mCancellationSignal != null) {
Log.w(TAG, "Enrolling has started, shall not start again");
return true;
Log.w(TAG, "Enrolling is running, shall not start again");
return mCancellationSignal;
}
if (DEBUG) {
Log.e(TAG, "startEnrollment(" + reason + ")");
@@ -204,7 +217,7 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, mEnrollmentCallback,
reason);
}
return true;
return mCancellationSignal;
}
/**
@@ -212,13 +225,17 @@ public class FingerprintEnrollProgressViewModel extends AndroidViewModel {
*/
public boolean cancelEnrollment() {
final CancellationSignal cancellationSignal = mCancellationSignal;
mCancellationSignal = null;
if (cancellationSignal == null) {
Log.e(TAG, "Fail to cancel enrollment, has cancelled or not start");
return false;
} else {
Log.d(TAG, "enrollment cancelled");
}
mCancellationSignal = null;
mCancelingSignalQueue.add(cancellationSignal);
cancellationSignal.cancel();
return true;
}

View File

@@ -23,6 +23,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.RotationUtils;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -130,18 +131,26 @@ public class UdfpsEnrollView extends FrameLayout {
onFingerUp();
}
private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::updateOverlayParams;
/**
* setup SensorProperties
*/
public void setSensorProperties(FingerprintSensorPropertiesInternal properties) {
mSensorProperties = properties;
((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(
new ViewTreeObserver.OnDrawListener() {
@Override
public void onDraw() {
updateOverlayParams();
}
});
((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(mOnDrawListener);
}
@Override
protected void onDetachedFromWindow() {
final ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
final ViewTreeObserver observer = parent.getViewTreeObserver();
if (observer != null) {
observer.removeOnDrawListener(mOnDrawListener);
}
}
super.onDetachedFromWindow();
}
private void onSensorRectUpdated() {
@@ -168,6 +177,10 @@ public class UdfpsEnrollView extends FrameLayout {
}
RelativeLayout parent = ((RelativeLayout) getParent());
if (parent == null) {
Log.e(TAG, "Fail to updateDimensions for " + this + ", parent null");
return;
}
final int[] coords = parent.getLocationOnScreen();
final int parentLeft = coords[0];
final int parentTop = coords[1];

View File

@@ -130,7 +130,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroChooseLock_landscape() {
fun testIntroChooseLock_runAslandscape() {
runAsLandscape = true
testIntroChooseLock()
}
@@ -193,7 +193,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_withUdfps_clickStart_landscape() {
fun testIntroWithGkPwHandle_withUdfps_clickStart_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_withUdfps_clickStart()
}
@@ -226,7 +226,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_withUdfps_clickLottie_landscape() {
fun testIntroWithGkPwHandle_withUdfps_clickLottie_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_withUdfps_clickLottie()
}
@@ -256,7 +256,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_withSfps_landscape() {
fun testIntroWithGkPwHandle_withSfps_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_withSfps()
}
@@ -291,7 +291,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_withRfps_landscape() {
fun testIntroWithGkPwHandle_withRfps_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_withRfps()
}
@@ -314,7 +314,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_clickNoThanksInIntroPage_landscape() {
fun testIntroWithGkPwHandle_clickNoThanksInIntroPage_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_clickNoThanksInIntroPage()
}
@@ -344,7 +344,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_clickSkipInFindSensor_landscape() {
fun testIntroWithGkPwHandle_clickSkipInFindSensor_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_clickSkipInFindSensor()
}
@@ -382,7 +382,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw_landscape() {
fun testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw()
}
@@ -418,7 +418,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw_landscape() {
fun testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw_runAslandscape() {
runAsLandscape = true
testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw()
}
@@ -449,7 +449,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testEnrollingWithGkPwHandle_landscape() {
fun testEnrollingWithGkPwHandle_runAslandscape() {
runAsLandscape = true
testEnrollingWithGkPwHandle()
}
@@ -492,7 +492,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testEnrollingIconTouchDialog_withSfps_landscape() {
fun testEnrollingIconTouchDialog_withSfps_runAslandscape() {
runAsLandscape = true
testEnrollingIconTouchDialog_withSfps()
}
@@ -534,7 +534,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testEnrollingIconTouchDialog_withRfps_landscape() {
fun testEnrollingIconTouchDialog_withRfps_runAslandscape() {
runAsLandscape = true
testEnrollingIconTouchDialog_withRfps()
}
@@ -563,7 +563,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testFindUdfpsWithGkPwHandle_clickStart_landscape() {
fun testFindUdfpsWithGkPwHandle_clickStart_runAslandscape() {
runAsLandscape = true
testFindUdfpsWithGkPwHandle_clickStart()
}
@@ -580,7 +580,11 @@ class FingerprintEnrollmentActivityTest {
assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue()
// rotate device
device.setOrientationLandscape()
if (runAsLandscape) {
device.setOrientationPortrait()
} else {
device.setOrientationLandscape()
}
device.waitForIdle()
// FindUdfps page (landscape)
@@ -605,6 +609,12 @@ class FingerprintEnrollmentActivityTest {
assertThat(device.wait(Until.hasObject(By.text(DO_IT_LATER)), IDLE_TIMEOUT)).isTrue()
}
@Test
fun testFindUdfpsLandscapeWithGkPwHandle_clickStartThenBack_runAslandscape() {
runAsLandscape = true
testFindUdfpsLandscapeWithGkPwHandle_clickStartThenBack()
}
@Test
fun testFindUdfpsWithGkPwHandle_clickLottie() {
Assume.assumeTrue(canAssumeUdfps)
@@ -629,7 +639,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testFindUdfpsWithGkPwHandle_clickLottie_landscape() {
fun testFindUdfpsWithGkPwHandle_clickLottie_runAslandscape() {
runAsLandscape = true
testFindUdfpsWithGkPwHandle_clickLottie()
}
@@ -653,7 +663,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testFindSfpsWithGkPwHandle_landscape() {
fun testFindSfpsWithGkPwHandle_runAslandscape() {
runAsLandscape = true
testFindSfpsWithGkPwHandle()
}
@@ -688,7 +698,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testFindRfpsWithGkPwHandle_landscape() {
fun testFindRfpsWithGkPwHandle_runAslandscape() {
runAsLandscape = true
testFindRfpsWithGkPwHandle()
}
@@ -712,7 +722,7 @@ class FingerprintEnrollmentActivityTest {
}
@Test
fun testFindSensorWithGkPwHandle_clickSkipInFindSensor_landscape() {
fun testFindSensorWithGkPwHandle_clickSkipInFindSensor_runAslandscape() {
runAsLandscape = true
testFindSensorWithGkPwHandle_clickSkipInFindSensor()
}

View File

@@ -18,13 +18,9 @@ package com.android.settings.biometrics2.ui.viewmodel;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.ErrorDialogData;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_BACK_PRESSED;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_CANCELED_BECAUSE_USER_SKIP;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT;
import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FingerprintErrorDialogAction;
import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository;
import static com.google.common.truth.Truth.assertThat;
@@ -78,19 +74,10 @@ public class FingerprintEnrollEnrollingViewModelTest {
mViewModel = new FingerprintEnrollEnrollingViewModel(
mApplication,
TEST_USER_ID,
newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5)
newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5)
);
}
@Test
public void testShowErrorDialogLiveData() {
assertThat(mViewModel.getErrorDialogLiveData().getValue()).isEqualTo(null);
final ErrorDialogData data = new ErrorDialogData("errMsg", "errTitle", 0);
mViewModel.showErrorDialog(data);
assertThat(mViewModel.getErrorDialogLiveData().getValue()).isEqualTo(data);
}
@Test
public void testIconTouchDialog() {
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();
@@ -101,20 +88,6 @@ public class FingerprintEnrollEnrollingViewModelTest {
FINGERPRINT_ENROLL_ENROLLING_ACTION_SHOW_ICON_TOUCH_DIALOG);
}
@Test
public void testErrorDialogActionLiveData() {
assertThat(mViewModel.getErrorDialogActionLiveData().getValue()).isEqualTo(null);
@FingerprintErrorDialogAction int action =
FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT;
mViewModel.onErrorDialogAction(action);
assertThat(mViewModel.getErrorDialogActionLiveData().getValue()).isEqualTo(action);
action = FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH;
mViewModel.onErrorDialogAction(action);
assertThat(mViewModel.getErrorDialogActionLiveData().getValue()).isEqualTo(action);
}
@Test
public void tesBackPressedScenario() {
final LiveData<Integer> actionLiveData = mViewModel.getActionLiveData();

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2023 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.viewmodel
import android.app.Application
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FingerprintEnrollErrorDialogViewModelTest {
private val application = ApplicationProvider.getApplicationContext<Application>()
private var viewModel: FingerprintEnrollErrorDialogViewModel =
FingerprintEnrollErrorDialogViewModel(application, false)
@Before
fun setUp() {
// Make sure viewModel is new for each test
viewModel = FingerprintEnrollErrorDialogViewModel(application, false)
}
@Test
fun testIsDialogNotShownDefaultFalse() {
assertThat(viewModel.isDialogShown).isFalse()
}
@Test
fun testIsSuw() {
assertThat(FingerprintEnrollErrorDialogViewModel(application, false).isSuw).isFalse()
assertThat(FingerprintEnrollErrorDialogViewModel(application, true).isSuw).isTrue()
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testNewDialog() = runTest {
backgroundScope.launch {
mutableListOf<Any>().let { list ->
viewModel.newDialogFlow.toList(list)
assertThat(list.size).isEqualTo(0)
}
mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
val testErrorMsgId = 3456
viewModel.newDialog(testErrorMsgId)
assertThat(viewModel.isDialogShown).isTrue()
viewModel.setResultFlow.toList(list)
assertThat(list.size).isEqualTo(1)
assertThat(list[0]).isEqualTo(testErrorMsgId)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testTriggerRetry() = runTest {
backgroundScope.launch {
// triggerRetryFlow shall be empty on begin
mutableListOf<Any>().let { list ->
viewModel.triggerRetryFlow.toList(list)
assertThat(list.size).isEqualTo(0)
}
// emit newDialog
mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
viewModel.newDialog(0)
viewModel.triggerRetry()
assertThat(viewModel.isDialogShown).isFalse()
viewModel.setResultFlow.toList(list)
assertThat(list.size).isEqualTo(1)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testSetResultFinish() = runTest {
backgroundScope.launch {
// setResultFlow shall be empty on begin
mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
viewModel.setResultFlow.toList(list)
assertThat(list.size).isEqualTo(0)
}
// emit FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
viewModel = FingerprintEnrollErrorDialogViewModel(application, false)
mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
viewModel.newDialog(0)
viewModel.setResultAndFinish(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
assertThat(viewModel.isDialogShown).isFalse()
viewModel.setResultFlow.toList(list)
assertThat(list.size).isEqualTo(1)
assertThat(list[0]).isEqualTo(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
}
// emit FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT
viewModel = FingerprintEnrollErrorDialogViewModel(application, false)
mutableListOf<FingerprintErrorDialogSetResultAction>().let { list ->
viewModel.newDialog(0)
viewModel.setResultAndFinish(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_TIMEOUT)
assertThat(viewModel.isDialogShown).isFalse()
viewModel.setResultFlow.toList(list)
assertThat(list.size).isEqualTo(1)
assertThat(list[0]).isEqualTo(FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH)
}
}
}
}

View File

@@ -108,9 +108,9 @@ public class FingerprintEnrollProgressViewModelTest {
mViewModel.setToken(token);
// Start enrollment
final boolean ret = mViewModel.startEnrollment(enrollReason);
final Object ret = mViewModel.startEnrollment(enrollReason);
assertThat(ret).isTrue();
assertThat(ret).isNotNull();
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
eq(TEST_USER_ID), any(EnrollmentCallback.class), eq(enrollReason));
assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isFalse();
@@ -123,9 +123,9 @@ public class FingerprintEnrollProgressViewModelTest {
mViewModel.setToken(token);
// Start enrollment
final boolean ret = mViewModel.startEnrollment(enrollReason);
final Object ret = mViewModel.startEnrollment(enrollReason);
assertThat(ret).isTrue();
assertThat(ret).isNotNull();
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
eq(TEST_USER_ID), any(EnrollmentCallback.class), eq(enrollReason));
assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isFalse();
@@ -142,9 +142,9 @@ public class FingerprintEnrollProgressViewModelTest {
mViewModel.setToken(token);
// Start enrollment
final boolean ret = mViewModel.startEnrollment(enrollReason);
final Object ret = mViewModel.startEnrollment(enrollReason);
assertThat(ret).isTrue();
assertThat(ret).isNotNull();
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
eq(TEST_USER_ID), any(MessageDisplayController.class), eq(enrollReason));
assertThat(mCallbackWrapper.mValue).isNotNull();
@@ -166,9 +166,9 @@ public class FingerprintEnrollProgressViewModelTest {
@Test
public void testStartEnrollmentFailBecauseOfNoToken() {
// Start enrollment
final boolean ret = mViewModel.startEnrollment(ENROLL_FIND_SENSOR);
final Object ret = mViewModel.startEnrollment(ENROLL_FIND_SENSOR);
assertThat(ret).isFalse();
assertThat(ret).isNull();
verify(mFingerprintUpdater, never()).enroll(any(byte[].class),
any(CancellationSignal.class), anyInt(), any(EnrollmentCallback.class), anyInt());
}
@@ -177,8 +177,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testCancelEnrollment() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCancellationSignalWrapper.mValue).isNotNull();
// Cancel enrollment
@@ -191,8 +191,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testProgressUpdate() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Test default value
@@ -228,8 +228,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testProgressUpdateClearHelpMessage() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
final LiveData<EnrollmentProgress> progressLiveData = mViewModel.getProgressLiveData();
final LiveData<EnrollmentStatusMessage> helpMsgLiveData =
@@ -271,8 +271,8 @@ public class FingerprintEnrollProgressViewModelTest {
mViewModel.setToken(new byte[] { 1, 2, 3 });
// Start enrollment
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Test default value
@@ -308,8 +308,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testGetErrorMessageLiveData() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Check default value
@@ -330,8 +330,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testGetHelpMessageLiveData() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Check default value
@@ -352,8 +352,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testGetAcquireLiveData() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Check default value
@@ -369,8 +369,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testGetPointerDownLiveData() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Check default value
@@ -387,8 +387,8 @@ public class FingerprintEnrollProgressViewModelTest {
public void testGetPointerUpLiveData() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isTrue();
final Object ret = mViewModel.startEnrollment(ENROLL_ENROLL);
assertThat(ret).isNotNull();
assertThat(mCallbackWrapper.mValue).isNotNull();
// Check default value