[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
diff --git a/Android.bp b/Android.bp
index 1351d03..baf9914 100644
--- a/Android.bp
+++ b/Android.bp
@@ -77,6 +77,7 @@
         "setupcompat",
         "setupdesign",
         "androidx.lifecycle_lifecycle-runtime",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.lifecycle_lifecycle-viewmodel",
         "guava",
         "jsr305",
diff --git a/res/layout/udfps_enroll_enrolling_v2.xml b/res/layout/udfps_enroll_enrolling_v2.xml
index 4675606..b579bed 100644
--- a/res/layout/udfps_enroll_enrolling_v2.xml
+++ b/res/layout/udfps_enroll_enrolling_v2.xml
@@ -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"
diff --git a/res/layout/udfps_enroll_enrolling_v2_udfps_view.xml b/res/layout/udfps_enroll_enrolling_v2_udfps_view.xml
new file mode 100644
index 0000000..a29b2fd
--- /dev/null
+++ b/res/layout/udfps_enroll_enrolling_v2_udfps_view.xml
@@ -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>
diff --git a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java
index dd5b673..b83614c 100644
--- a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java
+++ b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java
@@ -34,6 +34,7 @@
 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 @@
  */
 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 @@
             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 @@
             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);
     }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingErrorDialog.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingErrorDialog.kt
deleted file mode 100644
index 8fb1118..0000000
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingErrorDialog.kt
+++ /dev/null
@@ -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) }
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt
index 66a9c00..08a8217 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingRfpsFragment.kt
@@ -23,13 +23,14 @@
 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 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 @@
     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 @@
 
     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 @@
         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 @@
         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 @@
         }
 
         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 @@
     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 @@
     }
 
     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 @@
 
     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 @@
     }
 
     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 @@
         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 @@
                 )
                 .setDuration(100)
                 .setInterpolator(fastOutLinearInInterpolator)
-                .withEndAction { errorText!!.visibility = View.INVISIBLE }
+                .withEndAction { errorText.visibility = View.INVISIBLE }
                 .start()
         }
     }
@@ -385,8 +425,8 @@
 
     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
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt
index 980f800..fbdfc81 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingSfpsFragment.kt
@@ -21,10 +21,11 @@
 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 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 @@
     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 @@
         }
 
     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 @@
 
     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 @@
             }
             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 @@
     }
 
     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 @@
         }
 
     private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
+        Log.d(TAG, "onEnrollmentHelp($helpMessage)")
         val helpStr: CharSequence = helpMessage.str
         if (helpStr.isNotEmpty()) {
             showError(helpStr)
@@ -297,25 +343,26 @@
     }
 
     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 @@
         }
 
         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 @@
         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 @@
     }
 
     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 @@
 }
 
 fun LottieAnimationView.applyLottieDynamicColor(context: Context, isError: Boolean) {
-    addValueCallback<ColorFilter>(
+    addValueCallback(
         KeyPath(".blue100", "**"),
         LottieProperty.COLOR_FILTER
     ) {
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt
index a71c007..34b491d 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollEnrollingUdfpsFragment.kt
@@ -17,8 +17,8 @@
 
 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 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 @@
     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 @@
     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 @@
 
     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 @@
             override fun handleOnBackPressed() {
                 isEnabled = false
                 enrollingViewModel.setOnBackPressed()
-                cancelEnrollment()
+                cancelEnrollment(true)
             }
         }
 
@@ -156,6 +174,7 @@
             _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 @@
     ): 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 @@
         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 @@
 
     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 @@
         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 @@
             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 @@
         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 @@
             illustrationLottie = null
         } else if (shouldShowLottie) {
             illustrationLottie =
-                enrollingUdfpsView!!.findViewById<LottieAnimationView>(R.id.illustration_lottie)
+                enrollingView!!.findViewById(R.id.illustration_lottie)
         }
     }
 
@@ -468,6 +554,7 @@
     }
 
     private fun onEnrollmentHelp(helpMessage: EnrollmentStatusMessage) {
+        Log.d(TAG, "onEnrollmentHelp($helpMessage)")
         val helpStr: CharSequence = helpMessage.str
         if (helpStr.isNotEmpty()) {
             showError(helpStr)
@@ -476,25 +563,26 @@
     }
 
     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()
+            }
         }
     }
 
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollErrorDialog.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollErrorDialog.kt
new file mode 100644
index 0000000..882cbcf
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollErrorDialog.kt
@@ -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) }
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.kt
index 8f47abc..ee5ca56 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindRfpsFragment.kt
@@ -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.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 @@
     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 @@
 
     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 @@
             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 @@
 
     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 @@
             _viewModel = provider[FingerprintEnrollFindSensorViewModel::class.java]
             _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
             _rotationViewModel = provider[DeviceRotationViewModel::class.java]
+            _errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
         }
         super.onAttach(context)
     }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt
index 16dfefa..ab31dbc 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollFindSfpsFragment.kt
@@ -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 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 @@
     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 @@
             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 @@
 
     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 @@
         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 @@
 
     @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 @@
             _progressViewModel = provider[FingerprintEnrollProgressViewModel::class.java]
             _rotationViewModel = provider[DeviceRotationViewModel::class.java]
             _foldedViewModel = provider[DeviceFoldedViewModel::class.java]
+            _errorDialogViewModel = provider[FingerprintEnrollErrorDialogViewModel::class.java]
         }
         super.onAttach(context)
     }
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
index fd76198..12aac6a 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
@@ -32,8 +32,11 @@
 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.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.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 @@
         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 @@
         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 @@
         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 @@
         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 @@
     // 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 @@
         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 @@
 
     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 @@
     }
 
     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 @@
     }
 
     private fun attachFinishViewModel() {
-        viewModelProvider[FingerprintEnrollFinishViewModel::class.java].let {
+        finishViewModel.let {
             it.clearActionLiveData()
             it.actionLiveData.observe(this, finishActionObserver)
         }
@@ -520,18 +547,6 @@
         }
     }
 
-    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 @@
     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"
     }
 }
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java
index 3bed9fb..07fe275 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/DeviceRotationViewModel.java
@@ -59,9 +59,7 @@
         @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 @@
      * 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;
     }
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
index e2b2ee2..eba6a15 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModel.java
@@ -73,29 +73,11 @@
     @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 @@
     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 @@
         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 @@
         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 @@
     }
 
     /**
-     * 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 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 + "}";
-        }
-    }
 }
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModel.kt b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModel.kt
new file mode 100644
index 0000000..b154fe7
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModel.kt
@@ -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
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
index 7074288..9b25ee8 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
@@ -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.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 @@
             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 @@
 
     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 @@
 
         @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 @@
         return mErrorMessageLiveData;
     }
 
+    public LiveData<Object> getCanceledSignalLiveData() {
+        return mCanceledSignalLiveData;
+    }
+
     public LiveData<Boolean> getAcquireLiveData() {
         return mAcquireLiveData;
     }
@@ -167,14 +180,14 @@
     /**
      * 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 @@
             mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, mEnrollmentCallback,
                     reason);
         }
-        return true;
+        return mCancellationSignal;
     }
 
     /**
@@ -212,13 +225,17 @@
      */
     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;
     }
 
diff --git a/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java b/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java
index 518397a..55a78b8 100644
--- a/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java
+++ b/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java
@@ -23,6 +23,7 @@
 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 @@
         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 @@
         }
 
         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];
diff --git a/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt b/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt
index 33c8d3d..44eae91 100644
--- a/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt
+++ b/tests/uitests/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivityTest.kt
@@ -130,7 +130,7 @@
     }
 
     @Test
-    fun testIntroChooseLock_landscape() {
+    fun testIntroChooseLock_runAslandscape() {
         runAsLandscape = true
         testIntroChooseLock()
     }
@@ -193,7 +193,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_withUdfps_clickStart_landscape() {
+    fun testIntroWithGkPwHandle_withUdfps_clickStart_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_withUdfps_clickStart()
     }
@@ -226,7 +226,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_withUdfps_clickLottie_landscape() {
+    fun testIntroWithGkPwHandle_withUdfps_clickLottie_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_withUdfps_clickLottie()
     }
@@ -256,7 +256,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_withSfps_landscape() {
+    fun testIntroWithGkPwHandle_withSfps_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_withSfps()
     }
@@ -291,7 +291,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_withRfps_landscape() {
+    fun testIntroWithGkPwHandle_withRfps_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_withRfps()
     }
@@ -314,7 +314,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_clickNoThanksInIntroPage_landscape() {
+    fun testIntroWithGkPwHandle_clickNoThanksInIntroPage_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_clickNoThanksInIntroPage()
     }
@@ -344,7 +344,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_clickSkipInFindSensor_landscape() {
+    fun testIntroWithGkPwHandle_clickSkipInFindSensor_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_clickSkipInFindSensor()
     }
@@ -382,7 +382,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw_landscape() {
+    fun testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_clickSkipAnywayInFindFpsDialog_whenIsSuw()
     }
@@ -418,7 +418,7 @@
     }
 
     @Test
-    fun testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw_landscape() {
+    fun testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw_runAslandscape() {
         runAsLandscape = true
         testIntroWithGkPwHandle_clickGoBackInFindFpsDialog_whenIsSuw()
     }
@@ -449,7 +449,7 @@
     }
 
     @Test
-    fun testEnrollingWithGkPwHandle_landscape() {
+    fun testEnrollingWithGkPwHandle_runAslandscape() {
         runAsLandscape = true
         testEnrollingWithGkPwHandle()
     }
@@ -492,7 +492,7 @@
     }
 
     @Test
-    fun testEnrollingIconTouchDialog_withSfps_landscape() {
+    fun testEnrollingIconTouchDialog_withSfps_runAslandscape() {
         runAsLandscape = true
         testEnrollingIconTouchDialog_withSfps()
     }
@@ -534,7 +534,7 @@
     }
 
     @Test
-    fun testEnrollingIconTouchDialog_withRfps_landscape() {
+    fun testEnrollingIconTouchDialog_withRfps_runAslandscape() {
         runAsLandscape = true
         testEnrollingIconTouchDialog_withRfps()
     }
@@ -563,7 +563,7 @@
     }
 
     @Test
-    fun testFindUdfpsWithGkPwHandle_clickStart_landscape() {
+    fun testFindUdfpsWithGkPwHandle_clickStart_runAslandscape() {
         runAsLandscape = true
         testFindUdfpsWithGkPwHandle_clickStart()
     }
@@ -580,7 +580,11 @@
         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)
@@ -606,6 +610,12 @@
     }
 
     @Test
+    fun testFindUdfpsLandscapeWithGkPwHandle_clickStartThenBack_runAslandscape() {
+        runAsLandscape = true
+        testFindUdfpsLandscapeWithGkPwHandle_clickStartThenBack()
+    }
+
+    @Test
     fun testFindUdfpsWithGkPwHandle_clickLottie() {
         Assume.assumeTrue(canAssumeUdfps)
 
@@ -629,7 +639,7 @@
     }
 
     @Test
-    fun testFindUdfpsWithGkPwHandle_clickLottie_landscape() {
+    fun testFindUdfpsWithGkPwHandle_clickLottie_runAslandscape() {
         runAsLandscape = true
         testFindUdfpsWithGkPwHandle_clickLottie()
     }
@@ -653,7 +663,7 @@
     }
 
     @Test
-    fun testFindSfpsWithGkPwHandle_landscape() {
+    fun testFindSfpsWithGkPwHandle_runAslandscape() {
         runAsLandscape = true
         testFindSfpsWithGkPwHandle()
     }
@@ -688,7 +698,7 @@
     }
 
     @Test
-    fun testFindRfpsWithGkPwHandle_landscape() {
+    fun testFindRfpsWithGkPwHandle_runAslandscape() {
         runAsLandscape = true
         testFindRfpsWithGkPwHandle()
     }
@@ -712,7 +722,7 @@
     }
 
     @Test
-    fun testFindSensorWithGkPwHandle_clickSkipInFindSensor_landscape() {
+    fun testFindSensorWithGkPwHandle_clickSkipInFindSensor_runAslandscape() {
         runAsLandscape = true
         testFindSensorWithGkPwHandle_clickSkipInFindSensor()
     }
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModelTest.java
index a038747..d4fae60 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollEnrollingViewModelTest.java
@@ -18,13 +18,9 @@
 
 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,20 +74,11 @@
         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();
         assertThat(actionLiveData.getValue()).isEqualTo(null);
@@ -102,20 +89,6 @@
     }
 
     @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();
         assertThat(actionLiveData.getValue()).isEqualTo(null);
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt
new file mode 100644
index 0000000..ae829b9
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollErrorDialogViewModelTest.kt
@@ -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)
+            }
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java
index 2c830ad..418db04 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java
@@ -108,9 +108,9 @@
         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 @@
         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 @@
         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 @@
     @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 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 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 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 @@
         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 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 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 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 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 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