Merge "Fix full-charge timestamp lost in battery chart graph." into main
diff --git a/aconfig/OWNERS b/aconfig/OWNERS
index 1131545..c26a190 100644
--- a/aconfig/OWNERS
+++ b/aconfig/OWNERS
@@ -1 +1,3 @@
 per-file settings_accessibility_flag_declarations.aconfig = file:/src/com/android/settings/accessibility/OWNERS
+per-file settings_biometrics_integration_declarations.aconfig = file:platform/vendor/unbundled_google/packages/SettingsGoogle:/future/biometrics/OWNERS
+
diff --git a/aconfig/settings_voice_activation_apps_flag_declarations.aconfig b/aconfig/settings_voice_activation_apps_flag_declarations.aconfig
index dccc805..d98bc52 100644
--- a/aconfig/settings_voice_activation_apps_flag_declarations.aconfig
+++ b/aconfig/settings_voice_activation_apps_flag_declarations.aconfig
@@ -1,8 +1,8 @@
 package: "com.android.settings.flags"
 
 flag {
-  name: "enable_voice_activation_apps_special_app_access"
-  namespace: "voice_activation_apps"
-  description: "Enable voice activation apps in Special app access"
+  name: "enable_voice_activation_apps_in_settings"
+  namespace: "permissions"
+  description: "Enable voice activation apps in Settings"
   bug: "303727896"
 }
\ No newline at end of file
diff --git a/res/layout/fingerprint_v2_rfps_enroll_enrolling.xml b/res/layout/fingerprint_v2_rfps_enroll_enrolling.xml
new file mode 100644
index 0000000..0b087d2
--- /dev/null
+++ b/res/layout/fingerprint_v2_rfps_enroll_enrolling.xml
@@ -0,0 +1,75 @@
+<?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.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    style="?attr/fingerprint_layout_theme"
+    android:id="@+id/setup_wizard_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/SudContentFrame"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <com.google.android.setupdesign.view.FillContentLayout
+            android:layout_width="@dimen/fingerprint_progress_bar_max_size"
+            android:layout_height="@dimen/fingerprint_progress_bar_max_size"
+            android:layout_marginVertical="24dp"
+            android:paddingTop="0dp"
+            android:paddingBottom="0dp">
+
+            <com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@+id/fingerprint_progress_bar"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/fp_illustration"
+                android:minHeight="@dimen/fingerprint_progress_bar_min_size"
+                android:progress="0" />
+
+        </com.google.android.setupdesign.view.FillContentLayout>
+
+        <TextView
+            android:id="@+id/text"
+            style="@style/TextAppearance.ErrorText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal|bottom"
+            android:accessibilityLiveRegion="polite"
+            android:gravity="center"
+            android:visibility="invisible" />
+
+    </LinearLayout>
+
+</LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/res/layout/preference_widget_qrcode.xml b/res/layout/preference_widget_qrcode.xml
new file mode 100644
index 0000000..7994fe1
--- /dev/null
+++ b/res/layout/preference_widget_qrcode.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/button_icon"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:minWidth="@dimen/two_target_min_width"
+    android:minHeight="@dimen/min_tap_target_size"
+    android:layout_gravity="center"
+    android:background="?android:attr/selectableItemBackground"/>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1eb58ee..a3760c5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1212,7 +1212,7 @@
     <string name="private_space_hide_title">Hide when locked</string>
     <!-- Title for the hide Private Space setting. [CHAR LIMIT=60] -->
     <string name="privatespace_hide_page_title">Hide Private Space when locked</string>
-    <!-- Description for hide Private Space settings page. [CHAR LIMIT=60] -->
+    <!-- Description for hide Private Space settings page. [CHAR LIMIT=NONE] -->
     <string name="privatespace_hide_page_summary">To stop other people knowing Private Space is on your device, you can hide it from your apps list</string>
     <!-- Header in hide Private Space settings page to access Private Space when hidden. [CHAR LIMIT=60] -->
     <string name="privatespace_access_header">Access Private Space when hidden</string>
@@ -3169,6 +3169,8 @@
     <string name="apn_settings">APNs</string>
     <!-- Screen title after user selects APNs setting option -->
     <string name="apn_edit">Edit access point</string>
+    <!-- Screen title after user selects add APNs setting -->
+    <string name="apn_add">Add access point</string>
     <!-- Edit access point label summary text when no value has been set -->
     <string name="apn_not_set">Not set</string>
     <!-- Edit access point label summary text when no value has been set for mvno value. [CHAR LIMIT=NONE]-->
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
index 86bb062..ca7137a 100644
--- a/res/xml/bluetooth_audio_sharing.xml
+++ b/res/xml/bluetooth_audio_sharing.xml
@@ -26,6 +26,12 @@
         settings:controller="com.android.settings.connecteddevice.audiosharing.CallsAndAlarmsPreferenceController"
         android:summary=""/>
 
+    <com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
+        android:key="audio_sharing_stream_name"
+        android:title="Stream name"
+        android:summary="********"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController"/>
+
     <PreferenceCategory
         android:key="audio_streams_settings_category"
         android:title="@string/audio_sharing_streams_category_title"
@@ -38,5 +44,4 @@
             android:icon="@drawable/ic_chevron_right_24dp" />
 
     </PreferenceCategory>
-
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
index 98b7ed0..58ef509 100644
--- a/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/conversion/Util.kt
@@ -16,14 +16,61 @@
 
 package com.android.settings.biometrics.fingerprint2.conversion
 
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
 import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.R
 import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
 
-class Util
-
-fun EnrollReason.toOriginalReason(): Int {
-  return when (this) {
-    EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
-    EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
+object Util {
+  fun EnrollReason.toOriginalReason(): Int {
+    return when (this) {
+      EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
+      EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
+    }
   }
+
+  fun Int.toEnrollError(isSetupWizard: Boolean): FingerEnrollState.EnrollError {
+    val errTitle =
+      when (this) {
+        FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+          R.string.security_settings_fingerprint_enroll_error_dialog_title
+        FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+          R.string.security_settings_fingerprint_bad_calibration_title
+        else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
+      }
+    val errString =
+      if (isSetupWizard) {
+        when (this) {
+          FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+            R.string.security_settings_fingerprint_enroll_error_dialog_title
+          FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+            R.string.security_settings_fingerprint_bad_calibration_title
+          else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
+        }
+      } else {
+        when (this) {
+          // This message happens when the underlying crypto layer
+          // decides to revoke the enrollment auth token
+          FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
+            R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message
+          FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
+            R.string.security_settings_fingerprint_bad_calibration
+          FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS ->
+            R.string.security_settings_fingerprint_enroll_error_unable_to_process_message
+          // There's nothing specific to tell the user about. Ask them to try again.
+          else -> R.string.security_settings_fingerprint_enroll_error_generic_dialog_message
+        }
+      }
+
+    return FingerEnrollState.EnrollError(
+      errTitle,
+      errString,
+      this == FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+      this == FINGERPRINT_ERROR_CANCELED,
+    )
+  }
+
 }
+
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
index 5c9232f..984d04c 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
@@ -24,12 +24,16 @@
 import android.os.CancellationSignal
 import android.util.Log
 import com.android.settings.biometrics.GatekeeperPasswordProvider
-import com.android.settings.biometrics.fingerprint2.conversion.toOriginalReason
+import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
+import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
+import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
 import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
 import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
 import com.android.settings.password.ChooseLockSettingsHelper
 import com.android.systemui.biometrics.shared.model.toFingerprintSensor
 import kotlin.coroutines.resume
@@ -38,9 +42,12 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.channels.onFailure
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
@@ -51,7 +58,8 @@
   private val backgroundDispatcher: CoroutineDispatcher,
   private val fingerprintManager: FingerprintManager,
   private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
-  private val pressToAuthProvider: () -> Boolean,
+  private val pressToAuthProvider: PressToAuthProvider,
+  private val fingerprintFlow: FingerprintFlow,
 ) : FingerprintManagerInteractor {
 
   private val maxFingerprints =
@@ -60,6 +68,8 @@
     )
   private val applicationContext = applicationContext.applicationContext
 
+  private val enrollRequestOutstanding = MutableStateFlow(false)
+
   override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
     suspendCoroutine {
       val callback = GenerateChallengeCallback { _, userId, challenge ->
@@ -75,11 +85,11 @@
       fingerprintManager.generateChallenge(applicationContext.userId, callback)
     }
 
-  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+  override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
     emit(
       fingerprintManager
         .getEnrolledFingerprints(applicationContext.userId)
-        .map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) }
+        .map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
         .toList()
     )
   }
@@ -103,28 +113,51 @@
   override suspend fun enroll(
     hardwareAuthToken: ByteArray?,
     enrollReason: EnrollReason,
-  ): Flow<FingerEnrollStateViewModel> = callbackFlow {
+  ): Flow<FingerEnrollState> = callbackFlow {
+    // TODO (b/308456120) Improve this logic
+    if (enrollRequestOutstanding.value) {
+      Log.d(TAG, "Outstanding enroll request, waiting 150ms")
+      delay(150)
+      if (enrollRequestOutstanding.value) {
+        Log.e(TAG, "Request still present, continuing")
+      }
+    }
+
+    enrollRequestOutstanding.update { true }
+
     var streamEnded = false
+    var totalSteps: Int? = null
     val enrollmentCallback =
       object : FingerprintManager.EnrollmentCallback() {
         override fun onEnrollmentProgress(remaining: Int) {
-          trySend(FingerEnrollStateViewModel.EnrollProgress(remaining)).onFailure { error ->
+          // This is sort of an implementation detail, but unfortunately the API isn't
+          // very expressive. If anything we should look at changing the FingerprintManager API.
+          if (totalSteps == null) {
+            totalSteps = remaining + 1
+          }
+
+          trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure {
+            error ->
             Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
           }
+
           if (remaining == 0) {
             streamEnded = true
+            enrollRequestOutstanding.update { false }
           }
         }
 
         override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
-          trySend(FingerEnrollStateViewModel.EnrollHelp(helpMsgId, helpString.toString()))
+          trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString()))
             .onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
         }
 
         override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
-          trySend(FingerEnrollStateViewModel.EnrollError(errMsgId, errString.toString()))
+          trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard))
             .onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
+          Log.d(TAG, "onEnrollmentError($errMsgId)")
           streamEnded = true
+          enrollRequestOutstanding.update { false }
         }
       }
 
@@ -140,12 +173,13 @@
       // If the stream has not been ended, and the user has stopped collecting the flow
       // before it was over, send cancel.
       if (!streamEnded) {
+        Log.e(TAG, "Cancel is sent from settings for enroll()")
         cancellationSignal.cancel()
       }
     }
   }
 
-  override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
+  override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
     val callback =
       object : RemovalCallback() {
         override fun onRemovalError(
@@ -170,7 +204,7 @@
     )
   }
 
-  override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+  override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
     withContext(backgroundDispatcher) {
       fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
     }
@@ -181,11 +215,11 @@
   }
 
   override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
-    it.resume(pressToAuthProvider())
+    it.resume(pressToAuthProvider.isEnabled)
   }
 
-  override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
-    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
+  override suspend fun authenticate(): FingerprintAuthAttemptModel =
+    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
       val authenticationCallback =
         object : FingerprintManager.AuthenticationCallback() {
 
@@ -195,7 +229,7 @@
               Log.d(TAG, "framework sent down onAuthError after finish")
               return
             }
-            c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString()))
+            c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
           }
 
           override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
@@ -204,7 +238,7 @@
               Log.d(TAG, "framework sent down onAuthError after finish")
               return
             }
-            c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1))
+            c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
           }
         }
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
new file mode 100644
index 0000000..38c5335
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/repository/PressToAuthProviderImpl.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.biometrics.fingerprint2.repository
+
+import android.content.Context
+import android.provider.Settings
+import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
+
+class PressToAuthProviderImpl(val context: Context) : PressToAuthProvider {
+  override val isEnabled: Boolean
+    get() {
+      var toReturn: Int =
+        Settings.Secure.getIntForUser(
+          context.contentResolver,
+          Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+          -1,
+          context.userId,
+        )
+      if (toReturn == -1) {
+        toReturn =
+          if (
+            context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
+          ) {
+            1
+          } else {
+            0
+          }
+        Settings.Secure.putIntForUser(
+          context.contentResolver,
+          Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+          toReturn,
+          context.userId
+        )
+      }
+      return (toReturn == 1)
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
similarity index 60%
copy from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
copy to src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
index db28e79..e776b9a 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/data/repository/PressToAuthProvider.kt
@@ -14,21 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.biometrics.fingerprint2.shared.data.repository
 
-data class FingerprintViewModel(
-  val name: String,
-  val fingerId: Int,
-  val deviceId: Long,
-)
-
-sealed class FingerprintAuthAttemptViewModel {
-  data class Success(
-    val fingerId: Int,
-  ) : FingerprintAuthAttemptViewModel()
-
-  data class Error(
-    val error: Int,
-    val message: String,
-  ) : FingerprintAuthAttemptViewModel()
-}
+/**
+ * Interface that indicates if press to auth is on or off.
+ */
+interface PressToAuthProvider {
+    /**
+     * Indicates true if the PressToAuth feature is enabled, false otherwise.
+     */
+    val isEnabled: Boolean
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
index 7286715..94afa49 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/domain/interactor/FingerprintManagerInteractor.kt
@@ -17,9 +17,9 @@
 package com.android.settings.biometrics.fingerprint2.shared.domain.interactor
 
 import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
 import com.android.systemui.biometrics.shared.model.FingerprintSensor
 import kotlinx.coroutines.flow.Flow
 
@@ -31,7 +31,7 @@
  */
 interface FingerprintManagerInteractor {
   /** Returns the list of current fingerprints. */
-  val enrolledFingerprints: Flow<List<FingerprintViewModel>>
+  val enrolledFingerprints: Flow<List<FingerprintData>>
 
   /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
   val maxEnrollableFingerprints: Flow<Int>
@@ -43,7 +43,7 @@
   val sensorPropertiesInternal: Flow<FingerprintSensor?>
 
   /** Runs the authenticate flow */
-  suspend fun authenticate(): FingerprintAuthAttemptViewModel
+  suspend fun authenticate(): FingerprintAuthAttemptModel
 
   /**
    * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
@@ -56,22 +56,22 @@
 
   /**
    * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
-   * enrollment. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
+   * enrollment. Returning the [FingerEnrollState] that represents this fingerprint
    * enrollment state.
    */
   suspend fun enroll(
-    hardwareAuthToken: ByteArray?,
-    enrollReason: EnrollReason,
-  ): Flow<FingerEnrollStateViewModel>
+      hardwareAuthToken: ByteArray?,
+      enrollReason: EnrollReason,
+  ): Flow<FingerEnrollState>
 
   /**
    * Removes the given fingerprint, returning true if it was successfully removed and false
    * otherwise
    */
-  suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
+  suspend fun removeFingerprint(fp: FingerprintData): Boolean
 
   /** Renames the given fingerprint if one exists */
-  suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String)
+  suspend fun renameFingerprint(fp: FingerprintData, newName: String)
 
   /** Indicates if the device has side fingerprint */
   suspend fun hasSideFps(): Boolean
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReasonViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
similarity index 100%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReasonViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/EnrollReason.kt
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
similarity index 73%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollStateViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
index 179ac60..4766d59 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollStateViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerEnrollState.kt
@@ -22,19 +22,28 @@
  * Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
  * information
  */
-sealed class FingerEnrollStateViewModel {
-  /** Represents enrollment step progress. */
+sealed class FingerEnrollState {
+  /**
+   * Represents an enrollment step progress.
+   *
+   * Progress is obtained by (totalStepsRequired - remainingSteps) / totalStepsRequired
+   */
   data class EnrollProgress(
     val remainingSteps: Int,
-  ) : FingerEnrollStateViewModel()
+    val totalStepsRequired: Int,
+  ) : FingerEnrollState()
+
   /** Represents that recoverable error has been encountered during enrollment. */
   data class EnrollHelp(
     @StringRes val helpMsgId: Int,
     val helpString: String,
-  ) : FingerEnrollStateViewModel()
+  ) : FingerEnrollState()
+
   /** Represents that an unrecoverable error has been encountered and the operation is complete. */
   data class EnrollError(
-    @StringRes val errMsgId: Int,
-    val errString: String,
-  ) : FingerEnrollStateViewModel()
+    @StringRes val errTitle: Int,
+    @StringRes val errString: Int,
+    val shouldRetryEnrollment: Boolean,
+    val isCancelled: Boolean,
+  ) : FingerEnrollState()
 }
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
similarity index 84%
rename from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
rename to src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
index db28e79..b2aa25c 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintData.kt
@@ -16,19 +16,19 @@
 
 package com.android.settings.biometrics.fingerprint2.shared.model
 
-data class FingerprintViewModel(
+data class FingerprintData(
   val name: String,
   val fingerId: Int,
   val deviceId: Long,
 )
 
-sealed class FingerprintAuthAttemptViewModel {
+sealed class FingerprintAuthAttemptModel {
   data class Success(
     val fingerId: Int,
-  ) : FingerprintAuthAttemptViewModel()
+  ) : FingerprintAuthAttemptModel()
 
   data class Error(
     val error: Int,
     val message: String,
-  ) : FingerprintAuthAttemptViewModel()
+  ) : FingerprintAuthAttemptModel()
 }
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
new file mode 100644
index 0000000..93c7577
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintFlow.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.biometrics.fingerprint2.shared.model
+
+/**
+ * The [FingerprintFlow] for fingerprint enrollment indicates information on how the flow should behave.
+ */
+sealed class FingerprintFlow
+
+/** The default enrollment experience, typically called from Settings */
+data object Default : FingerprintFlow()
+
+/** SetupWizard/Out of box experience (OOBE) enrollment type. */
+data object SetupWizard : FingerprintFlow()
+
+/** Unicorn enrollment type */
+data object Unicorn : FingerprintFlow()
+
+/** Flow to specify settings type */
+data object Settings : FingerprintFlow()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index 58fcea6..de2a1ee 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -16,12 +16,9 @@
 
 package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity
 
-import android.annotation.ColorInt
 import android.app.Activity
 import android.content.Intent
-import android.content.res.ColorStateList
 import android.content.res.Configuration
-import android.graphics.Color
 import android.hardware.fingerprint.FingerprintManager
 import android.os.Bundle
 import android.provider.Settings
@@ -35,22 +32,27 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.settings.R
 import com.android.settings.SetupWizardUtils
-import com.android.settings.Utils
 import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
 import com.android.settings.biometrics.BiometricEnrollBase
 import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
 import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
 import com.android.settings.biometrics.GatekeeperPasswordProvider
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
+import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
@@ -65,8 +67,11 @@
 import com.android.settings.password.ChooseLockGeneric
 import com.android.settings.password.ChooseLockSettingsHelper
 import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.google.android.setupcompat.util.WizardManagerHelper
 import com.google.android.setupdesign.util.ThemeHelper
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.launch
 
@@ -77,6 +82,7 @@
  * children fragments.
  */
 class FingerprintEnrollmentV2Activity : FragmentActivity() {
+  private lateinit var fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
   private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
   private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
   private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
@@ -84,6 +90,7 @@
   private lateinit var foldStateViewModel: FoldStateViewModel
   private lateinit var orientationStateViewModel: OrientationStateViewModel
   private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
+  private lateinit var backgroundViewModel: BackgroundViewModel
   private val coroutineDispatcher = Dispatchers.Default
 
   /** Result listener for ChooseLock activity flow. */
@@ -101,23 +108,22 @@
     }
   }
 
-  override fun onAttachedToWindow() {
-    window.statusBarColor = getBackgroundColor()
-    super.onAttachedToWindow()
+  override fun onStop() {
+    super.onStop()
+    if (!isChangingConfigurations) {
+      backgroundViewModel.wentToBackground()
+    }
   }
 
+  override fun onResume() {
+    super.onResume()
+    backgroundViewModel.inForeground()
+  }
   override fun onConfigurationChanged(newConfig: Configuration) {
     super.onConfigurationChanged(newConfig)
     foldStateViewModel.onConfigurationChange(newConfig)
   }
 
-  @ColorInt
-  private fun getBackgroundColor(): Int {
-    val stateList: ColorStateList? =
-      Utils.getColorAttr(applicationContext, android.R.attr.windowBackground)
-    return stateList?.defaultColor ?: Color.TRANSPARENT
-  }
-
   private fun onConfirmDevice(resultCode: Int, data: Intent?) {
     val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
     val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
@@ -137,39 +143,28 @@
 
     val context = applicationContext
     val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
+    val isAnySuw = WizardManagerHelper.isAnySetupWizard(intent)
+    val enrollType =
+      if (isAnySuw) {
+        SetupWizard
+      } else {
+        Default
+      }
+
+    backgroundViewModel =
+      ViewModelProvider(this, BackgroundViewModel.BackgroundViewModelFactory())[
+        BackgroundViewModel::class.java]
+
 
     val interactor =
       FingerprintManagerInteractorImpl(
         context,
         backgroundDispatcher,
         fingerprintManager,
-        GatekeeperPasswordProvider(LockPatternUtils(context))
-      ) {
-        var toReturn: Int =
-          Settings.Secure.getIntForUser(
-            context.contentResolver,
-            Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-            -1,
-            context.userId,
-          )
-        if (toReturn == -1) {
-          toReturn =
-            if (
-              context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
-            ) {
-              1
-            } else {
-              0
-            }
-          Settings.Secure.putIntForUser(
-            context.contentResolver,
-            Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-            toReturn,
-            context.userId
-          )
-        }
-        toReturn == 1
-      }
+        GatekeeperPasswordProvider(LockPatternUtils(context)),
+        PressToAuthProviderImpl(context),
+        enrollType,
+      )
 
     var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
     val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
@@ -191,7 +186,8 @@
           backgroundDispatcher,
           interactor,
           gatekeeperViewModel,
-          gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
+          gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo,
+          enrollType,
         )
       )[FingerprintEnrollNavigationViewModel::class.java]
 
@@ -207,7 +203,8 @@
         this,
         FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
           interactor,
-          backgroundDispatcher
+          gatekeeperViewModel,
+          navigationViewModel,
         )
       )[FingerprintEnrollViewModel::class.java]
 
@@ -230,6 +227,16 @@
       ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
         OrientationStateViewModel::class.java]
 
+    // Initialize FingerprintEnrollEnrollingViewModel
+    fingerprintEnrollEnrollingViewModel =
+      ViewModelProvider(
+        this,
+        FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
+          fingerprintEnrollViewModel,
+          backgroundViewModel
+        )
+      )[FingerprintEnrollEnrollingViewModel::class.java]
+
     // Initialize FingerprintEnrollFindSensorViewModel
     ViewModelProvider(
       this,
@@ -237,48 +244,65 @@
         navigationViewModel,
         fingerprintEnrollViewModel,
         gatekeeperViewModel,
+        backgroundViewModel,
         accessibilityViewModel,
         foldStateViewModel,
         orientationStateViewModel
       )
     )[FingerprintEnrollFindSensorViewModel::class.java]
 
+    // Initialize RFPS View Model
+    ViewModelProvider(
+      this,
+      RFPSViewModel.RFPSViewModelFactory(fingerprintEnrollEnrollingViewModel)
+    )[RFPSViewModel::class.java]
+
     lifecycleScope.launch {
-      navigationViewModel.navigationViewModel.filterNotNull().collect {
-        Log.d(TAG, "navigationStep $it")
-        val isForward = it.forward
-        val currStep = it.currStep
-        val theClass: Class<Fragment>? =
-          when (currStep) {
-            Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
-            Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
-            Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
-            Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
-            else -> null
-          }
-
-        if (theClass != null) {
-          supportFragmentManager.fragments.onEach { fragment ->
-            supportFragmentManager.beginTransaction().remove(fragment).commit()
-          }
-          supportFragmentManager
-            .beginTransaction()
-            .setReorderingAllowed(true)
-            .add(R.id.fragment_container_view, theClass, null)
-            .commit()
-        } else {
-
-          if (currStep is Finish) {
-            if (currStep.resultCode != null) {
-              finishActivity(currStep.resultCode)
-            } else {
-              finish()
+      navigationViewModel.navigationViewModel
+        .filterNotNull()
+        .combine(fingerprintEnrollViewModel.sensorType) { nav, sensorType -> Pair(nav, sensorType) }
+        .collect { (nav, sensorType) ->
+          Log.d(TAG, "navigationStep $nav")
+          fingerprintEnrollViewModel.sensorTypeCached = sensorType
+          val isForward = nav.forward
+          val currStep = nav.currStep
+          val theClass: Class<Fragment>? =
+            when (currStep) {
+              Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
+              Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
+              is Enrollment -> {
+                when (sensorType) {
+                  FingerprintSensorType.REAR -> RFPSEnrollFragment::class.java as Class<Fragment>
+                  else -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
+                }
+              }
+              Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
+              else -> null
             }
-          } else if (currStep == LaunchConfirmDeviceCredential) {
-            launchConfirmOrChooseLock(userId)
+
+          if (theClass != null) {
+            supportFragmentManager.fragments.onEach { fragment ->
+              supportFragmentManager.beginTransaction().remove(fragment).commit()
+            }
+
+            supportFragmentManager
+              .beginTransaction()
+              .setReorderingAllowed(true)
+              .add(R.id.fragment_container_view, theClass, null)
+              .commit()
+          } else {
+
+            if (currStep is Finish) {
+              if (currStep.resultCode != null) {
+                finishActivity(currStep.resultCode)
+              } else {
+                finish()
+              }
+            } else if (currStep == LaunchConfirmDeviceCredential) {
+              launchConfirmOrChooseLock(userId)
+            }
           }
         }
-      }
     }
 
     val fromSettingsSummary =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
index 0afa613..bfd4264 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -30,6 +30,7 @@
 import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
 import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.google.android.setupcompat.template.FooterBarMixin
 import com.google.android.setupcompat.template.FooterButton
@@ -54,23 +55,8 @@
   private var animation: FingerprintFindSensorAnimation? = null
 
   private var contentLayoutId: Int = -1
-  private lateinit var viewModel: FingerprintEnrollFindSensorViewModel
-
-  override fun onCreate(savedInstanceState: Bundle?) {
-    super.onCreate(savedInstanceState)
-    viewModel =
-      ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
-    lifecycleScope.launch {
-      viewModel.sensorType.collect {
-        contentLayoutId =
-          when (it) {
-            FingerprintSensorType.UDFPS_OPTICAL,
-            FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
-            FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
-            else -> R.layout.fingerprint_v2_enroll_find_sensor
-          }
-      }
-    }
+  private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
+    ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
   }
 
   override fun onCreateView(
@@ -78,6 +64,18 @@
     container: ViewGroup?,
     savedInstanceState: Bundle?
   ): View? {
+
+    val sensorType =
+      ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java].sensorTypeCached
+
+    contentLayoutId =
+      when (sensorType) {
+        FingerprintSensorType.UDFPS_OPTICAL,
+        FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
+        FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
+        else -> R.layout.fingerprint_v2_enroll_find_sensor
+      }
+
     return inflater.inflate(contentLayoutId, container, false).also { it ->
       val view = it!! as GlifLayout
 
@@ -106,7 +104,8 @@
       }
       lifecycleScope.launch {
         viewModel.showRfpsAnimation.collect {
-          animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
+          animation =
+            view.findViewById(R.id.fingerprint_sensor_location_animation)
           animation!!.startAnimation()
         }
       }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
index 898b158..b1ab301 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollIntroV2Fragment.kt
@@ -36,11 +36,11 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.Unicorn
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.google.android.setupcompat.template.FooterBarMixin
 import com.google.android.setupcompat.template.FooterButton
@@ -120,7 +120,7 @@
 
       viewLifecycleOwner.lifecycleScope.launch {
         combine(
-            navigationViewModel.enrollType,
+            navigationViewModel.fingerprintFlow,
             fingerprintViewModel.sensorType,
           ) { enrollType, sensorType ->
             Pair(enrollType, sensorType)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md
new file mode 100644
index 0000000..dfb9598
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/README.md
@@ -0,0 +1,26 @@
+# Module enrollment
+
+### Fingerprint Settings Enrollment Modules
+
+This directory is responsible for containing the enrollment modules, each enrollment module is
+responsible for the actual enrolling portion of FingerprintEnrollment.
+The modules should be split out into udfps, rfps, and sfps.
+
+[comment]: <>  This file structure print out has been generated with the tree command.
+
+```
+├── enrolling
+│   └── rfps
+│       ├── data
+│       ├── domain
+│       │   └── RFPSInteractor.kt
+│       ├── README.md
+│       └── ui
+│           ├── fragment
+│           │   └── RFPSEnrollFragment.kt
+│           ├── viewmodel
+│           │   └── RFPSViewModel.kt
+│           └── widget
+│               └── RFPSProgressIndicator.kt
+└── README.md
+```
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
new file mode 100644
index 0000000..d8c2f5a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment
+
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import android.view.animation.Interpolator
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import com.google.android.setupcompat.template.FooterBarMixin
+import com.google.android.setupcompat.template.FooterButton
+import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+private const val TAG = "RFPSEnrollFragment"
+
+/** This fragment is responsible for taking care of rear fingerprint enrollment. */
+class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrolling) {
+
+  private lateinit var linearOutSlowInInterpolator: Interpolator
+  private lateinit var fastOutLinearInInterpolator: Interpolator
+  private lateinit var textView: TextView
+  private lateinit var progressBar: RFPSProgressBar
+
+  private val iconTouchViewModel: RFPSIconTouchViewModel by lazy {
+    ViewModelProvider(requireActivity())[RFPSIconTouchViewModel::class.java]
+  }
+
+  private val orientationViewModel: OrientationStateViewModel by lazy {
+    ViewModelProvider(requireActivity())[OrientationStateViewModel::class.java]
+  }
+
+  private val rfpsViewModel: RFPSViewModel by lazy {
+    ViewModelProvider(requireActivity())[RFPSViewModel::class.java]
+  }
+
+  private val backgroundViewModel: BackgroundViewModel by lazy {
+    ViewModelProvider(requireActivity())[BackgroundViewModel::class.java]
+  }
+
+  override fun onCreateView(
+    inflater: LayoutInflater,
+    container: ViewGroup?,
+    savedInstanceState: Bundle?
+  ): View? {
+    val view = super.onCreateView(inflater, container, savedInstanceState)!!
+    val fragment = this
+    val context = requireContext()
+    val glifLayout = view.requireViewById(R.id.setup_wizard_layout) as GlifLayout
+    glifLayout.setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message)
+    glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title)
+
+    fastOutLinearInInterpolator =
+      AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in)
+    linearOutSlowInInterpolator =
+      AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in)
+
+    textView = view.requireViewById(R.id.text) as TextView
+    progressBar = view.requireViewById(R.id.fingerprint_progress_bar) as RFPSProgressBar
+
+    val footerBarMixin = glifLayout.getMixin(FooterBarMixin::class.java)
+    footerBarMixin.secondaryButton =
+      FooterButton.Builder(context)
+        .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
+        .setListener { Log.e(TAG, "skip enrollment!") }
+        .setButtonType(FooterButton.ButtonType.SKIP)
+        .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+        .build()
+    footerBarMixin.buttonContainer.setBackgroundColor(Color.TRANSPARENT)
+
+    progressBar.setOnTouchListener { _, motionEvent ->
+      if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
+        iconTouchViewModel.userTouchedFingerprintIcon()
+      }
+      true
+    }
+
+    // On any orientation event, dismiss dialogs.
+    viewLifecycleOwner.lifecycleScope.launch {
+      orientationViewModel.orientation.collect { dismissDialogs() }
+    }
+
+    // Signal we are ready for enrollment.
+    rfpsViewModel.readyForEnrollment()
+
+    viewLifecycleOwner.lifecycleScope.launch {
+      repeatOnLifecycle(Lifecycle.State.RESUMED) {
+        // Icon animation  update
+        viewLifecycleOwner.lifecycleScope.launch {
+          rfpsViewModel.shouldAnimateIcon.collect { animate ->
+            progressBar.updateIconAnimation(animate)
+          }
+        }
+
+        // Flow to show a dialog.
+        viewLifecycleOwner.lifecycleScope.launch {
+          iconTouchViewModel.shouldShowDialog.collectLatest { showDialog ->
+            if (showDialog) {
+              try {
+                IconTouchDialog.showInstance(fragment)
+              } catch (exception: Exception) {
+                Log.d(TAG, "Dialog dismissed due to $exception")
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // If we go to the background, then finish enrollment. This should be permanent finish,
+    // and shouldn't be reset until we explicitly tell the view model we want to retry
+    // enrollment.
+    viewLifecycleOwner.lifecycleScope.launch {
+      backgroundViewModel.background
+        .filter { inBackground -> inBackground }
+        .collect { rfpsViewModel.stopEnrollment() }
+    }
+
+    viewLifecycleOwner.lifecycleScope.launch {
+      rfpsViewModel.progress.filterNotNull().collect { progress -> handleEnrollProgress(progress) }
+    }
+
+    viewLifecycleOwner.lifecycleScope.launch {
+      rfpsViewModel.helpMessage.filterNotNull().collect { help ->
+        textView.text = help.helpString
+        textView.visibility = View.VISIBLE
+        textView.translationY =
+          resources.getDimensionPixelSize(R.dimen.fingerprint_error_text_appear_distance).toFloat()
+        textView.alpha = 0f
+        textView
+          .animate()
+          .alpha(1f)
+          .translationY(0f)
+          .setDuration(200)
+          .setInterpolator(linearOutSlowInInterpolator)
+          .start()
+
+      }
+    }
+
+    viewLifecycleOwner.lifecycleScope.launch {
+      rfpsViewModel.errorMessage.filterNotNull().collect { error -> handleEnrollError(error) }
+    }
+    viewLifecycleOwner.lifecycleScope.launch {
+      rfpsViewModel.textViewIsVisible.collect {
+        textView.visibility = if (it) View.VISIBLE else View.INVISIBLE
+      }
+    }
+
+    viewLifecycleOwner.lifecycleScope.launch {
+      rfpsViewModel.clearHelpMessage.collect {
+        textView
+          .animate()
+          .alpha(0f)
+          .translationY(
+            resources
+              .getDimensionPixelSize(R.dimen.fingerprint_error_text_disappear_distance)
+              .toFloat()
+          )
+          .setDuration(100)
+          .setInterpolator(fastOutLinearInInterpolator)
+          .withEndAction { rfpsViewModel.setVisibility(false) }
+          .start()
+      }
+    }
+
+    viewLifecycleOwner.lifecycleScope.launch {
+      repeatOnLifecycle(Lifecycle.State.DESTROYED) {
+        rfpsViewModel.stopEnrollment()
+        dismissDialogs()
+      }
+    }
+    return view
+  }
+
+  private fun handleEnrollError(error: FingerEnrollState.EnrollError) {
+    val fragment = this
+    viewLifecycleOwner.lifecycleScope.launch {
+      try {
+        val shouldRestartEnrollment = FingerprintErrorDialog.showInstance(error, fragment)
+      } catch (exception: Exception) {
+        Log.e(TAG, "Exception occurred $exception")
+      }
+      onEnrollmentFailed()
+    }
+  }
+
+  private fun onEnrollmentFailed() {
+    rfpsViewModel.stopEnrollment()
+  }
+
+  private fun handleEnrollProgress(progress: FingerEnrollState.EnrollProgress) {
+    progressBar.updateProgress(
+      progress.remainingSteps.toFloat() / progress.totalStepsRequired.toFloat()
+    )
+
+    if (progress.remainingSteps == 0) {
+      performNextStepSuccess()
+    }
+  }
+
+  private fun performNextStepSuccess() {}
+
+  private fun dismissDialogs() {
+    val transaction = parentFragmentManager.beginTransaction()
+    for (frag in parentFragmentManager.fragments) {
+      if (frag is InstrumentedDialogFragment) {
+        Log.d(TAG, "removing dialog settings fragment $frag")
+        frag.dismiss()
+        transaction.remove(frag)
+      }
+    }
+    transaction.commitAllowingStateLoss()
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
new file mode 100644
index 0000000..c16e65c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSIconTouchViewModel.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+
+private const val touchesToShowDialog = 3
+/**
+ * This class is responsible for counting the number of touches on the fingerprint icon, and if this
+ * number reaches a threshold it will produce an action via [shouldShowDialog] to indicate the ui
+ * should show a dialog.
+ */
+class RFPSIconTouchViewModel : ViewModel() {
+
+  /** Keeps the number of times a user has touches the fingerprint icon. */
+  private val _touches: MutableStateFlow<Int> = MutableStateFlow(0)
+
+  /**
+   * Whether or not the UI should be showing the dialog. By making this SharingStarted.Eagerly
+   * the first event 0 % 3 == 0 will fire as soon as this view model is created, so it should
+   * be ignored and work as intended.
+   */
+  val shouldShowDialog: Flow<Boolean> =
+    _touches
+      .transform { numTouches -> emit((numTouches % touchesToShowDialog) == 0) }
+      .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+
+  /** Indicates a user has tapped on the fingerprint icon. */
+  fun userTouchedFingerprintIcon() {
+    _touches.update { _touches.value + 1 }
+  }
+
+  class RFPSIconTouchViewModelFactory : ViewModelProvider.Factory {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+      return RFPSIconTouchViewModel() as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
new file mode 100644
index 0000000..58d604e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.flow.update
+
+/** View Model used by the rear fingerprint enrollment fragment. */
+class RFPSViewModel(
+  private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
+) : ViewModel() {
+
+  /** Value to indicate if the text view is visible or not **/
+  private val _textViewIsVisible = MutableStateFlow<Boolean>(false)
+  val textViewIsVisible: Flow<Boolean> = _textViewIsVisible.asStateFlow()
+
+  /** Indicates if the icon should be animating or not */
+  val shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
+
+  private val enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFLow
+
+  /**
+   * Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
+   * recent state (this is useful for things like screen rotation)
+   */
+  val progress: Flow<FingerEnrollState.EnrollProgress?> =
+    enrollFlow
+      .filterIsInstance<FingerEnrollState.EnrollProgress>()
+      .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
+
+  /** Clear help message on enroll progress */
+  val clearHelpMessage: Flow<Boolean> = progress.map { it != null }
+
+  /** Enroll help message that is only displayed once */
+  val helpMessage: Flow<FingerEnrollState.EnrollHelp?> =
+    enrollFlow
+      .filterIsInstance<FingerEnrollState.EnrollHelp>()
+      .shareIn(viewModelScope, SharingStarted.Eagerly, 0).transform {
+        _textViewIsVisible.update { true }
+      }
+
+  /**
+   * The error message should only be shown once, for scenarios like screen rotations, we don't want
+   * to re-show the error message.
+   */
+  val errorMessage: Flow<FingerEnrollState.EnrollError?> =
+    enrollFlow
+      .filterIsInstance<FingerEnrollState.EnrollError>()
+      .shareIn(viewModelScope, SharingStarted.Eagerly, 0)
+
+  /** Indicates if the consumer is ready for enrollment */
+  fun readyForEnrollment() {
+    fingerprintEnrollViewModel.canEnroll()
+  }
+
+  /** Indicates if enrollment should stop */
+  fun stopEnrollment() {
+    fingerprintEnrollViewModel.stopEnroll()
+  }
+
+  fun setVisibility(isVisible: Boolean) {
+    _textViewIsVisible.update { isVisible }
+  }
+
+  class RFPSViewModelFactory(
+    private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
+  ) : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+      return RFPSViewModel(fingerprintEnrollEnrollingViewModel) as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
new file mode 100644
index 0000000..b9c628e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "FingerprintErrorDialog"
+
+/** A Dialog used for fingerprint enrollment when an error occurs. */
+class FingerprintErrorDialog : InstrumentedDialogFragment() {
+  private lateinit var onContinue: DialogInterface.OnClickListener
+  private lateinit var onTryAgain: DialogInterface.OnClickListener
+  private lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+  override fun onCancel(dialog: DialogInterface) {
+    Log.d(TAG, "onCancel $dialog")
+    onCancelListener.onCancel(dialog)
+  }
+
+  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+    Log.d(TAG, "onCreateDialog $this")
+    val errorString = requireArguments().getInt(KEY_MESSAGE)
+    val errorTitle = requireArguments().getInt(KEY_TITLE)
+    val builder = AlertDialog.Builder(requireContext())
+    val shouldShowTryAgain = requireArguments().getBoolean(KEY_SHOULD_TRY_AGAIN)
+    builder.setTitle(errorTitle).setMessage(errorString).setCancelable(false)
+
+    if (shouldShowTryAgain) {
+      builder
+        .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_try_again) {
+          dialog,
+          which ->
+          dialog.dismiss()
+          onTryAgain.onClick(dialog, which)
+        }
+        .setNegativeButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which
+          ->
+          dialog.dismiss()
+          onContinue.onClick(dialog, which)
+        }
+    } else {
+      builder.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) {
+        dialog,
+        which ->
+        dialog.dismiss()
+        onContinue.onClick(dialog, which)
+      }
+    }
+
+    val dialog = builder.create()
+    dialog.setCanceledOnTouchOutside(false)
+    return dialog
+  }
+
+  override fun getMetricsCategory(): Int {
+    return SettingsEnums.DIALOG_FINGERPINT_ERROR
+  }
+
+  companion object {
+    private const val KEY_MESSAGE = "fingerprint_message"
+    private const val KEY_TITLE = "fingerprint_title"
+    private const val KEY_SHOULD_TRY_AGAIN = "should_try_again"
+
+    suspend fun showInstance(
+        error: FingerEnrollState.EnrollError,
+        fragment: Fragment,
+    ) = suspendCancellableCoroutine { continuation ->
+      val dialog = FingerprintErrorDialog()
+      dialog.onTryAgain = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
+
+      dialog.onContinue = DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
+
+      dialog.onCancelListener =
+        DialogInterface.OnCancelListener {
+          Log.d(TAG, "onCancelListener clicked $dialog")
+          continuation.resume(null)
+        }
+
+      continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
+
+      val bundle = Bundle()
+      bundle.putInt(
+        KEY_TITLE,
+        error.errTitle,
+      )
+      bundle.putInt(
+        KEY_MESSAGE,
+        error.errString,
+      )
+      bundle.putBoolean(
+        KEY_SHOULD_TRY_AGAIN,
+        error.shouldRetryEnrollment,
+      )
+      dialog.arguments = bundle
+      Log.d(TAG, "showing dialog $dialog")
+      dialog.show(fragment.parentFragmentManager, FingerprintErrorDialog::class.java.toString())
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt
new file mode 100644
index 0000000..c086343
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/IconTouchDialog.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import com.android.settings.R
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "IconTouchDialog"
+
+/** Dialog shown when the user taps the Progress bar a certain amount of times. */
+class IconTouchDialog : InstrumentedDialogFragment() {
+  lateinit var onDismissListener: DialogInterface.OnClickListener
+  lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+  override fun onCancel(dialog: DialogInterface) {
+    Log.d(TAG, "onCancel $dialog")
+    onCancelListener.onCancel(dialog)
+  }
+
+  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+    val builder: AlertDialog.Builder = AlertDialog.Builder(activity, R.style.Theme_AlertDialog)
+    builder
+      .setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
+      .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
+      .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which ->
+        dialog.dismiss()
+        onDismissListener.onClick(dialog, which)
+      }
+      .setOnCancelListener { onCancelListener.onCancel(it) }
+    return builder.create()
+  }
+
+  override fun getMetricsCategory(): Int {
+    return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH
+  }
+
+  companion object {
+    suspend fun showInstance(fragment: Fragment) = suspendCancellableCoroutine { continuation ->
+      val dialog = IconTouchDialog()
+      dialog.onDismissListener =
+        DialogInterface.OnClickListener { _, _ -> continuation.resume("Done") }
+      dialog.onCancelListener =
+        DialogInterface.OnCancelListener { _ -> continuation.resume("OnCancel") }
+
+      continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
+
+      dialog.show(fragment.parentFragmentManager, IconTouchDialog::class.java.toString())
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
new file mode 100644
index 0000000..fe62681
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/RFPSProgressBar.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.util.AttributeSet
+import android.view.animation.AnimationUtils
+import android.view.animation.Interpolator
+import com.android.settings.R
+import com.android.settings.widget.RingProgressBar
+
+/** Progress bar for rear fingerprint enrollment. */
+class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
+  RingProgressBar(context, attributeSet) {
+
+  private val fastOutSlowInInterpolator: Interpolator
+
+  private val iconAnimationDrawable: AnimatedVectorDrawable
+  private val iconBackgroundBlinksDrawable: AnimatedVectorDrawable
+
+  private val maxProgress: Int
+
+  private var progressAnimation: ObjectAnimator? = null
+
+  private var shouldAnimateInternal: Boolean = true
+
+  init {
+    val fingerprintDrawable = background as LayerDrawable
+    iconAnimationDrawable =
+      fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation)
+        as AnimatedVectorDrawable
+    iconBackgroundBlinksDrawable =
+      fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background)
+        as AnimatedVectorDrawable
+
+    fastOutSlowInInterpolator =
+      AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in)
+
+    iconAnimationDrawable.registerAnimationCallback(
+      object : Animatable2.AnimationCallback() {
+        override fun onAnimationEnd(drawable: Drawable?) {
+          super.onAnimationEnd(drawable)
+          if (shouldAnimateInternal) {
+            animateIconAnimationInternal()
+          }
+        }
+      }
+    )
+    animateIconAnimationInternal()
+
+    progressBackgroundTintMode = PorterDuff.Mode.SRC
+
+    val attributes =
+      context.obtainStyledAttributes(R.style.RingProgressBarStyle, intArrayOf(android.R.attr.max))
+
+    maxProgress = attributes.getInt(0, -1)
+
+    attributes.recycle()
+  }
+
+  /** Indicates if the progress animation should be running */
+  fun updateIconAnimation(shouldAnimate: Boolean) {
+    if (shouldAnimate && !shouldAnimateInternal) {
+      animateIconAnimationInternal()
+    }
+
+    shouldAnimateInternal = shouldAnimate
+  }
+
+  /** This function should only be called when actual progress has been made. */
+  fun updateProgress(percentComplete: Float) {
+    val progress = maxProgress - (percentComplete.coerceIn(0.0f, 100.0f) * maxProgress).toInt()
+    iconBackgroundBlinksDrawable.start()
+
+    progressAnimation?.isRunning?.let { progressAnimation!!.cancel() }
+
+    progressAnimation = ObjectAnimator.ofInt(this, "progress", getProgress(), progress)
+
+    progressAnimation?.interpolator = fastOutSlowInInterpolator
+    progressAnimation?.setDuration(250)
+    progressAnimation?.start()
+  }
+
+  private fun animateIconAnimationInternal() {
+    iconAnimationDrawable.start()
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt
new file mode 100644
index 0000000..2b53a53
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/BackgroundViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** A class for determining if the application is in the background or not. */
+class BackgroundViewModel : ViewModel() {
+
+  private val _background = MutableStateFlow(false)
+  /** When true, the application is in background, else false */
+  val background = _background.asStateFlow()
+
+  /** Indicates that the application has been put in the background. */
+  fun wentToBackground() {
+    _background.update { true }
+  }
+
+  /** Indicates that the application has been brought to the foreground. */
+  fun inForeground() {
+    _background.update { false }
+  }
+
+  class BackgroundViewModelFactory : ViewModelProvider.Factory {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+      return BackgroundViewModel() as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
new file mode 100644
index 0000000..7ab315e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.flow.update
+
+/**
+ * This class is a wrapper around the [FingerprintEnrollViewModel] and decides when
+ * the user should or should not be enrolling.
+ */
+class FingerprintEnrollEnrollingViewModel(
+  private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+  backgroundViewModel: BackgroundViewModel,
+) : ViewModel() {
+
+  private val _didTryEnrollment = MutableStateFlow(false)
+  private val _userDidEnroll = MutableStateFlow(false)
+  /** Indicates if the enrollment flow should be running. */
+  val enrollFlowShouldBeRunning: Flow<Boolean> =
+    _userDidEnroll.combine(backgroundViewModel.background) { shouldEnroll, isInBackground ->
+      if (isInBackground) {
+        false
+      } else {
+        shouldEnroll
+      }
+    }
+
+  /**
+   * Used to indicate the consumer of the view model is ready for an enrollment. Note that this does
+   * not necessarily try an enrollment.
+   */
+  fun canEnroll() {
+    // Update _consumerShouldEnroll after updating the other values.
+    if (!_didTryEnrollment.value) {
+      _didTryEnrollment.update { true }
+      _userDidEnroll.update { true }
+    }
+  }
+
+  /** Used to indicate to stop the enrollment. */
+  fun stopEnroll() {
+    _userDidEnroll.update { false }
+  }
+
+  /** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */
+  val enrollFLow =
+    enrollFlowShouldBeRunning.transformLatest {
+      if (it) {
+        fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
+      }
+    }
+
+  class FingerprintEnrollEnrollingViewModelFactory(
+    private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+    private val backgroundViewModel: BackgroundViewModel
+  ) : ViewModelProvider.Factory {
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+      return FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)
+        as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
index 90aefc8..7722a46 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
@@ -16,12 +16,11 @@
 
 package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
 
-import android.hardware.fingerprint.FingerprintManager
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,6 +39,7 @@
   private val navigationViewModel: FingerprintEnrollNavigationViewModel,
   private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
   private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+  backgroundViewModel: BackgroundViewModel,
   accessibilityViewModel: AccessibilityViewModel,
   foldStateViewModel: FoldStateViewModel,
   orientationStateViewModel: OrientationStateViewModel
@@ -88,6 +88,14 @@
   /** Represents the stream of showing error dialog. */
   val showErrorDialog = _showErrorDialog.filterNotNull()
 
+  private var _didTryEducation = false
+  private var _education: MutableStateFlow<Boolean> = MutableStateFlow(false)
+  /** Indicates if the education flow should be running. */
+  private val educationFlowShouldBeRunning: Flow<Boolean> =
+    _education.combine(backgroundViewModel.background) { shouldRunEducation, isInBackground ->
+      !isInBackground && shouldRunEducation
+    }
+
   init {
     // Start or end enroll flow
     viewModelScope.launch {
@@ -107,40 +115,58 @@
         }
         .collect { token ->
           if (token != null) {
-            fingerprintEnrollViewModel.startEnroll(token, EnrollReason.FindSensor)
+            canStartEducation()
           } else {
-            fingerprintEnrollViewModel.stopEnroll()
+            stopEducation()
           }
         }
     }
 
     // Enroll progress flow
     viewModelScope.launch {
-      combine(
-          navigationViewModel.enrollType,
-          fingerprintEnrollViewModel.enrollFlow.filterNotNull()
-        ) { enrollType, enrollFlow ->
-          Pair(enrollType, enrollFlow)
-        }
-        .collect { (enrollType, enrollFlow) ->
-          when (enrollFlow) {
-            // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding to
-            // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
-            is FingerEnrollStateViewModel.EnrollProgress -> proceedToEnrolling()
-            is FingerEnrollStateViewModel.EnrollError -> {
-              val errMsgId = enrollFlow.errMsgId
-              if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
-                proceedToEnrolling()
-              } else {
-                _showErrorDialog.update { Pair(errMsgId, enrollType == SetupWizard) }
+      educationFlowShouldBeRunning.collect {
+        // Only collect the flow when we should be running.
+        if (it) {
+          combine(
+              navigationViewModel.fingerprintFlow,
+              fingerprintEnrollViewModel.educationEnrollFlow.filterNotNull(),
+            ) { enrollType, educationFlow ->
+              Pair(enrollType, educationFlow)
+            }
+            .collect { (enrollType, educationFlow) ->
+              when (educationFlow) {
+                // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding
+                // to
+                // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
+                is FingerEnrollState.EnrollProgress -> proceedToEnrolling()
+                is FingerEnrollState.EnrollError -> {
+                  if (educationFlow.isCancelled) {
+                    proceedToEnrolling()
+                  } else {
+                    _showErrorDialog.update { Pair(educationFlow.errString, enrollType == SetupWizard) }
+                  }
+                }
+                is FingerEnrollState.EnrollHelp -> {}
               }
             }
-            is FingerEnrollStateViewModel.EnrollHelp -> {}
-          }
         }
+      }
     }
   }
 
+  /** Indicates if education can begin */
+  private fun canStartEducation() {
+    if (!_didTryEducation) {
+      _didTryEducation = true
+      _education.update { true }
+    }
+  }
+
+  /** Indicates that education has finished */
+  private fun stopEducation() {
+    _education.update { false }
+  }
+
   /** Proceed to EnrollEnrolling page. */
   fun proceedToEnrolling() {
     navigationViewModel.nextStep()
@@ -150,6 +176,7 @@
     private val navigationViewModel: FingerprintEnrollNavigationViewModel,
     private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
     private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+    private val backgroundViewModel: BackgroundViewModel,
     private val accessibilityViewModel: AccessibilityViewModel,
     private val foldStateViewModel: FoldStateViewModel,
     private val orientationStateViewModel: OrientationStateViewModel
@@ -160,6 +187,7 @@
         navigationViewModel,
         fingerprintEnrollViewModel,
         gatekeeperViewModel,
+        backgroundViewModel,
         accessibilityViewModel,
         foldStateViewModel,
         orientationStateViewModel
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
index 392d205..c7a1071 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -17,32 +17,41 @@
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
 import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
 import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.transformLatest
-import kotlinx.coroutines.flow.update
-
-private const val TAG = "FingerprintEnrollViewModel"
 
 /** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
 class FingerprintEnrollViewModel(
   private val fingerprintManagerInteractor: FingerprintManagerInteractor,
-  backgroundDispatcher: CoroutineDispatcher,
+  gatekeeperViewModel: FingerprintGatekeeperViewModel,
+  navigationViewModel: FingerprintEnrollNavigationViewModel,
 ) : ViewModel() {
 
-  private var _enrollReason: MutableStateFlow<EnrollReason> =
-    MutableStateFlow(EnrollReason.FindSensor)
-  private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null)
-  private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false)
+  /**
+   * Cached value of [FingerprintSensorType]
+   *
+   * This is typically used by fragments that change their layout/behavior based on this
+   * information. This value should be set before any fragment is created.
+   */
+  var sensorTypeCached: FingerprintSensorType? = null
+  private var _enrollReason: Flow<EnrollReason?> =
+    navigationViewModel.navigationViewModel.map {
+      when (it.currStep) {
+        is Enrollment -> EnrollReason.EnrollEnrolling
+        is Education -> EnrollReason.FindSensor
+        else -> null
+      }
+    }
 
   /** Represents the stream of [FingerprintSensorType] */
   val sensorType: Flow<FingerprintSensorType> =
@@ -51,47 +60,68 @@
   /**
    * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
    * an enrollment process
+   *
+   * This flow should be the only flow which calls enroll().
    */
-  val enrollFlow: Flow<FingerEnrollStateViewModel> =
-    combine(_consumerShouldEnroll, _hardwareAuthToken, _enrollReason) {
-        consumerShouldEnroll,
-        hardwareAuthToken,
-        enrollReason ->
-        Triple(consumerShouldEnroll, hardwareAuthToken, enrollReason)
+  val _enrollFlow: Flow<FingerEnrollState> =
+    combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason,
+        ->
+        Pair(hardwareAuthToken, enrollReason)
       }
       .transformLatest {
-        // transformLatest() instead of transform() is used here for cancelling previous enroll()
-        // whenever |consumerShouldEnroll| is changed. Otherwise the latest value will be suspended
-        // since enroll() is an infinite callback flow.
-        (consumerShouldEnroll, hardwareAuthToken, enrollReason) ->
-        if (consumerShouldEnroll && hardwareAuthToken != null) {
-          fingerprintManagerInteractor.enroll(hardwareAuthToken, enrollReason).collect { emit(it) }
+        /** [transformLatest] is used as we want to make sure to cancel previous API call. */
+        (hardwareAuthToken, enrollReason) ->
+        if (hardwareAuthToken is GatekeeperInfo.GatekeeperPasswordInfo && enrollReason != null) {
+          fingerprintManagerInteractor.enroll(hardwareAuthToken.token, enrollReason).collect {
+            emit(it)
+          }
         }
       }
-      .flowOn(backgroundDispatcher)
+      .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
 
-  /** Used to indicate the consumer of the view model is ready for an enrollment. */
-  fun startEnroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) {
-    _enrollReason.update { enrollReason }
-    _hardwareAuthToken.update { hardwareAuthToken }
-    // Update _consumerShouldEnroll after updating the other values.
-    _consumerShouldEnroll.update { true }
-  }
+  /**
+   * This flow will kick off education when
+   * 1) There is an active subscriber to this flow
+   * 2) shouldEnroll is true and we are on the FindSensor step
+   */
+  val educationEnrollFlow: Flow<FingerEnrollState?> =
+    _enrollReason.filterNotNull().transformLatest { enrollReason ->
+      if (enrollReason == EnrollReason.FindSensor) {
+        _enrollFlow.collect { event -> emit(event) }
+      } else {
+        emit(null)
+      }
+    }
 
-  /** Used to indicate to stop the enrollment. */
-  fun stopEnroll() {
-    _consumerShouldEnroll.update { false }
-  }
+  /**
+   * This flow will kick off enrollment when
+   * 1) There is an active subscriber to this flow
+   * 2) shouldEnroll is true and we are on the EnrollEnrolling step
+   */
+  val enrollFlow: Flow<FingerEnrollState?> =
+    _enrollReason.filterNotNull().transformLatest { enrollReason ->
+      if (enrollReason == EnrollReason.EnrollEnrolling) {
+        _enrollFlow.collect { event -> emit(event) }
+      } else {
+        emit(null)
+      }
+    }
 
   class FingerprintEnrollViewModelFactory(
     val interactor: FingerprintManagerInteractor,
-    val backgroundDispatcher: CoroutineDispatcher
+    val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+    val navigationViewModel: FingerprintEnrollNavigationViewModel,
   ) : ViewModelProvider.Factory {
     @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel> create(
       modelClass: Class<T>,
     ): T {
-      return FingerprintEnrollViewModel(interactor, backgroundDispatcher) as T
+      return FingerprintEnrollViewModel(
+        interactor,
+        gatekeeperViewModel,
+        navigationViewModel,
+      )
+        as T
     }
   }
 }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
index 97c8271..2e5dce0 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrolllNavigationViewModel.kt
@@ -21,31 +21,20 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
 import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 private const val TAG = "FingerprintEnrollNavigationViewModel"
 
 /**
- * The [EnrollType] for fingerprint enrollment indicates information on how the flow should behave.
- */
-sealed class EnrollType
-
-/** The default enrollment experience, typically called from Settings */
-object Default : EnrollType()
-
-/** SetupWizard/Out of box experience (OOBE) enrollment type. */
-object SetupWizard : EnrollType()
-
-/** Unicorn enrollment type */
-object Unicorn : EnrollType()
-
-/**
  * This class is responsible for sending a [NavigationStep] which indicates where the user is in the
  * Fingerprint Enrollment flow
  */
@@ -53,31 +42,26 @@
   private val dispatcher: CoroutineDispatcher,
   private val fingerprintManagerInteractor: FingerprintManagerInteractor,
   private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
-  private val canSkipConfirm: Boolean
+  private val firstStep: NextStepViewModel,
+  private val navState: NavState,
+  private val theFingerprintFlow: FingerprintFlow,
 ) : ViewModel() {
 
   private class InternalNavigationStep(
     lastStep: NextStepViewModel,
     nextStep: NextStepViewModel,
     forward: Boolean,
-    var canNavigate: Boolean
+    var canNavigate: Boolean,
   ) : NavigationStep(lastStep, nextStep, forward)
 
-  private var _enrollType = MutableStateFlow<EnrollType?>(Default)
+  private var _fingerprintFlow = MutableStateFlow<FingerprintFlow?>(theFingerprintFlow)
 
-  /** A flow that indicates the [EnrollType] */
-  val enrollType: Flow<EnrollType?> = _enrollType.asStateFlow()
-
-  private var navState = NavState(canSkipConfirm)
+  /** A flow that indicates the [FingerprintFlow] */
+  val fingerprintFlow: Flow<FingerprintFlow?> = _fingerprintFlow.asStateFlow()
 
   private val _navigationStep =
     MutableStateFlow(
-      InternalNavigationStep(
-        PlaceHolderState,
-        Start.next(navState),
-        forward = false,
-        canNavigate = true
-      )
+      InternalNavigationStep(PlaceHolderState, firstStep, forward = false, canNavigate = true)
     )
 
   init {
@@ -96,6 +80,10 @@
    */
   val navigationViewModel: Flow<NavigationStep> = _navigationStep.asStateFlow()
 
+  /** This action indicates that the UI should actually update the navigation to the given step. */
+  val navigationAction: Flow<NavigationStep?> =
+    _navigationStep.shareIn(viewModelScope, SharingStarted.Lazily, 0)
+
   /** Used to start the next step of Fingerprint Enrollment. */
   fun nextStep() {
     viewModelScope.launch {
@@ -130,6 +118,7 @@
     private val fingerprintManagerInteractor: FingerprintManagerInteractor,
     private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
     private val canSkipConfirm: Boolean,
+    private val fingerprintFlow: FingerprintFlow,
   ) : ViewModelProvider.Factory {
 
     @Suppress("UNCHECKED_CAST")
@@ -137,11 +126,14 @@
       modelClass: Class<T>,
     ): T {
 
+      val navState = NavState(canSkipConfirm)
       return FingerprintEnrollNavigationViewModel(
         backgroundDispatcher,
         fingerprintManagerInteractor,
         fingerprintGatekeeperViewModel,
-        canSkipConfirm,
+        Start.next(navState),
+        navState,
+        fingerprintFlow,
       )
         as T
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
index e99b8f9..b68f6d6 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/NextStepViewModel.kt
@@ -57,7 +57,7 @@
  * This state is the initial state for the current step, and will be used to determine if the user
  * needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro]
  */
-object Start : NextStepViewModel() {
+data object Start : NextStepViewModel() {
   override fun next(state: NavState): NextStepViewModel =
     if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
 
@@ -71,19 +71,19 @@
 }
 
 /** State for the FingerprintEnrollment introduction */
-object Intro : NextStepViewModel() {
+data object Intro : NextStepViewModel() {
   override fun next(state: NavState): NextStepViewModel = Education
   override fun prev(state: NavState): NextStepViewModel = Finish(null)
 }
 
 /** State for the FingerprintEnrollment education */
-object Education : NextStepViewModel() {
+data object Education : NextStepViewModel() {
   override fun next(state: NavState): NextStepViewModel = Enrollment
   override fun prev(state: NavState): NextStepViewModel = Intro
 }
 
 /** State for the FingerprintEnrollment enrollment */
-object Enrollment : NextStepViewModel() {
+data object Enrollment : NextStepViewModel() {
   override fun next(state: NavState): NextStepViewModel = Confirmation
   override fun prev(state: NavState): NextStepViewModel = Education
 }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
index e66b4cd..debdfb8 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
@@ -19,8 +19,8 @@
 import android.hardware.fingerprint.FingerprintManager
 import android.util.Log
 import androidx.lifecycle.LifecycleCoroutineScope
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
@@ -66,21 +66,21 @@
     /** Indicates what result should be set for the returning callee */
     fun setResultExternal(resultCode: Int)
     /** Indicates the settings UI should be shown */
-    fun showSettings(enrolledFingerprints: List<FingerprintViewModel>)
+    fun showSettings(enrolledFingerprints: List<FingerprintData>)
     /** Updates the add fingerprints preference */
     fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int)
     /** Updates the sfps fingerprints preference */
     fun updateSfpsPreference(isSfpsPrefVisible: Boolean)
     /** Indicates that a user has been locked out */
-    fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
+    fun userLockout(authAttemptViewModel: FingerprintAuthAttemptModel.Error)
     /** Indicates a fingerprint preference should be highlighted */
     suspend fun highlightPref(fingerId: Int)
     /** Indicates a user should be prompted to delete a fingerprint */
-    suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean
+    suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintData): Boolean
     /** Indicates a user should be asked to renae ma dialog */
     suspend fun askUserToRenameDialog(
-      fingerprintViewModel: FingerprintViewModel
-    ): Pair<FingerprintViewModel, String>?
+      fingerprintViewModel: FingerprintData
+    ): Pair<FingerprintData, String>?
   }
 
   fun bind(
@@ -131,10 +131,10 @@
     lifecycleScope.launch {
       viewModel.authFlow.filterNotNull().collect {
         when (it) {
-          is FingerprintAuthAttemptViewModel.Success -> {
+          is FingerprintAuthAttemptModel.Success -> {
             view.highlightPref(it.fingerId)
           }
-          is FingerprintAuthAttemptViewModel.Error -> {
+          is FingerprintAuthAttemptModel.Error -> {
             if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
               view.userLockout(it)
             }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
index 32b50c5..71a22eb 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintDeletionDialog.kt
@@ -26,7 +26,7 @@
 import android.os.UserManager
 import androidx.appcompat.app.AlertDialog
 import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment
 import kotlin.coroutines.resume
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -34,7 +34,7 @@
 private const val KEY_IS_LAST_FINGERPRINT = "IS_LAST_FINGERPRINT"
 
 class FingerprintDeletionDialog : InstrumentedDialogFragment() {
-  private lateinit var fingerprintViewModel: FingerprintViewModel
+  private lateinit var fingerprintViewModel: FingerprintData
   private var isLastFingerprint: Boolean = false
   private lateinit var alertDialog: AlertDialog
   lateinit var onClickListener: DialogInterface.OnClickListener
@@ -51,7 +51,7 @@
 
   override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
     val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
-    fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId)
+    fingerprintViewModel = FingerprintData(fp.name.toString(), fp.biometricId, fp.deviceId)
     isLastFingerprint = requireArguments().getBoolean(KEY_IS_LAST_FINGERPRINT)
     val title = getString(R.string.fingerprint_delete_title, fingerprintViewModel.name)
     var message = getString(R.string.fingerprint_v2_delete_message, fingerprintViewModel.name)
@@ -95,9 +95,9 @@
   companion object {
     private const val KEY_FINGERPRINT = "fingerprint"
     suspend fun showInstance(
-      fp: FingerprintViewModel,
-      lastFingerprint: Boolean,
-      target: FingerprintSettingsV2Fragment,
+        fp: FingerprintData,
+        lastFingerprint: Boolean,
+        target: FingerprintSettingsV2Fragment,
     ) = suspendCancellableCoroutine { continuation ->
       val dialog = FingerprintDeletionDialog()
       dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
index b1e5097..ea26946 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsPreference.kt
@@ -22,7 +22,7 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.preference.PreferenceViewHolder
 import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settingslib.widget.TwoTargetPreference
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
@@ -30,10 +30,10 @@
 private const val TAG = "FingerprintSettingsPreference"
 
 class FingerprintSettingsPreference(
-  context: Context,
-  val fingerprintViewModel: FingerprintViewModel,
-  val fragment: FingerprintSettingsV2Fragment,
-  val isLastFingerprint: Boolean
+    context: Context,
+    val fingerprintViewModel: FingerprintData,
+    val fragment: FingerprintSettingsV2Fragment,
+    val isLastFingerprint: Boolean
 ) : TwoTargetPreference(context) {
   private lateinit var myView: View
 
@@ -79,7 +79,7 @@
     return FingerprintDeletionDialog.showInstance(fingerprintViewModel, isLastFingerprint, fragment)
   }
 
-  suspend fun askUserToRenameDialog(): Pair<FingerprintViewModel, String>? {
+  suspend fun askUserToRenameDialog(): Pair<FingerprintData, String>? {
     return FingerprintSettingsRenameDialog.showInstance(fingerprintViewModel, fragment)
   }
 }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
index 9bde0b0..ff469f1 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsRenameDialog.kt
@@ -27,7 +27,7 @@
 import android.widget.ImeAwareEditText
 import androidx.appcompat.app.AlertDialog
 import com.android.settings.R
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment
 import kotlin.coroutines.resume
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -46,7 +46,7 @@
   override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
     Log.d(TAG, "onCreateDialog $this")
     val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
-    val fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId)
+    val fingerprintViewModel = FingerprintData(fp.name.toString(), fp.biometricId, fp.deviceId)
 
     val context = requireContext()
     val alertDialog =
@@ -101,7 +101,7 @@
   companion object {
     private const val KEY_FINGERPRINT = "fingerprint"
 
-    suspend fun showInstance(fp: FingerprintViewModel, target: FingerprintSettingsV2Fragment) =
+    suspend fun showInstance(fp: FingerprintData, target: FingerprintSettingsV2Fragment) =
       suspendCancellableCoroutine { continuation ->
         val dialog = FingerprintSettingsRenameDialog()
         val onClick =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
index c818566..c22a5a7 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
@@ -46,8 +46,10 @@
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.shared.model.Settings
 import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
@@ -142,7 +144,7 @@
       }
     }
 
-  override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) {
+  override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptModel.Error) {
     Toast.makeText(activity, authAttemptViewModel.message, Toast.LENGTH_SHORT).show()
   }
 
@@ -186,40 +188,46 @@
     val backgroundDispatcher = Dispatchers.IO
     val activity = requireActivity()
     val userHandle = activity.user.identifier
+    // Note that SUW should not be launching FingerprintSettings
+    val isAnySuw = Settings
+
+    val pressToAuthProvider = {
+      var toReturn: Int =
+        Secure.getIntForUser(
+          context.contentResolver,
+          Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+          -1,
+          userHandle,
+        )
+      if (toReturn == -1) {
+        toReturn =
+          if (
+            context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
+          ) {
+            1
+          } else {
+            0
+          }
+        Secure.putIntForUser(
+          context.contentResolver,
+          Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+          toReturn,
+          userHandle
+        )
+      }
+
+      toReturn == 1
+    }
 
     val interactor =
       FingerprintManagerInteractorImpl(
         context.applicationContext,
         backgroundDispatcher,
         fingerprintManager,
-        GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext))
-      ) {
-        var toReturn: Int =
-          Secure.getIntForUser(
-            context.contentResolver,
-            Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-            -1,
-            userHandle,
-          )
-        if (toReturn == -1) {
-          toReturn =
-            if (
-              context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
-            ) {
-              1
-            } else {
-              0
-            }
-          Secure.putIntForUser(
-            context.contentResolver,
-            Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-            toReturn,
-            userHandle
-          )
-        }
-
-        toReturn == 1
-      }
+        GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
+        PressToAuthProviderImpl(context),
+        isAnySuw
+      )
 
     val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
     val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
@@ -292,18 +300,18 @@
   }
 
   /** Used to indicate that preference has been clicked */
-  fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
+  fun onPrefClicked(fingerprintViewModel: FingerprintData) {
     Log.d(TAG, "onPrefClicked(${fingerprintViewModel})")
     settingsViewModel.onPrefClicked(fingerprintViewModel)
   }
 
   /** Used to indicate that a delete pref has been clicked */
-  fun onDeletePrefClicked(fingerprintViewModel: FingerprintViewModel) {
+  fun onDeletePrefClicked(fingerprintViewModel: FingerprintData) {
     Log.d(TAG, "onDeletePrefClicked(${fingerprintViewModel})")
     settingsViewModel.onDeleteClicked(fingerprintViewModel)
   }
 
-  override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) {
+  override fun showSettings(enrolledFingerprints: List<FingerprintData>) {
     val category =
       this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
         as PreferenceCategory?
@@ -422,7 +430,7 @@
     }
   }
 
-  override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean {
+  override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintData): Boolean {
     Log.d(TAG, "showing delete dialog for (${fingerprintViewModel})")
 
     try {
@@ -446,8 +454,8 @@
   }
 
   override suspend fun askUserToRenameDialog(
-    fingerprintViewModel: FingerprintViewModel
-  ): Pair<FingerprintViewModel, String>? {
+    fingerprintViewModel: FingerprintData
+  ): Pair<FingerprintData, String>? {
     Log.d(TAG, "showing rename dialog for (${fingerprintViewModel})")
     try {
       val toReturn =
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
index fa1e5e1..164f79f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
@@ -22,8 +22,8 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
 import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -53,11 +53,11 @@
   private val backgroundDispatcher: CoroutineDispatcher,
   private val navigationViewModel: FingerprintSettingsNavigationViewModel,
 ) : ViewModel() {
-  private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> =
+  private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
     MutableStateFlow(null)
 
   /** Represents the stream of enrolled fingerprints. */
-  val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
+  val enrolledFingerprints: Flow<List<FingerprintData>> =
     _enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown()
 
   /** Represents the stream of the information of "Add Fingerprint" preference. */
@@ -95,10 +95,10 @@
   private val _sensorNullOrEmpty: Flow<Boolean> =
     fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
 
-  private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
+  private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
     MutableStateFlow(null)
 
-  private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
+  private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptModel.Success?> =
     MutableSharedFlow()
 
   private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
@@ -164,7 +164,7 @@
       .distinctUntilChanged()
 
   /** Represents a consistent stream of authentication attempts. */
-  val authFlow: Flow<FingerprintAuthAttemptViewModel> =
+  val authFlow: Flow<FingerprintAuthAttemptModel> =
     canAuthenticate
       .transformLatest {
         try {
@@ -173,11 +173,11 @@
             Log.d(TAG, "canAuthenticate authing")
             attemptingAuth()
             when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
-              is FingerprintAuthAttemptViewModel.Success -> {
+              is FingerprintAuthAttemptModel.Success -> {
                 onAuthSuccess(authAttempt)
                 emit(authAttempt)
               }
-              is FingerprintAuthAttemptViewModel.Error -> {
+              is FingerprintAuthAttemptModel.Error -> {
                 if (authAttempt.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
                   lockout(authAttempt)
                   emit(authAttempt)
@@ -219,7 +219,7 @@
   }
 
   /** The fingerprint delete button has been clicked. */
-  fun onDeleteClicked(fingerprintViewModel: FingerprintViewModel) {
+  fun onDeleteClicked(fingerprintViewModel: FingerprintData) {
     viewModelScope.launch {
       if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
         _isShowingDialog.tryEmit(PreferenceViewModel.DeleteDialog(fingerprintViewModel))
@@ -230,7 +230,7 @@
   }
 
   /** The rename fingerprint dialog has been clicked. */
-  fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
+  fun onPrefClicked(fingerprintViewModel: FingerprintData) {
     viewModelScope.launch {
       if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
         _isShowingDialog.tryEmit(PreferenceViewModel.RenameDialog(fingerprintViewModel))
@@ -241,7 +241,7 @@
   }
 
   /** A request to delete a fingerprint */
-  fun deleteFingerprint(fp: FingerprintViewModel) {
+  fun deleteFingerprint(fp: FingerprintData) {
     viewModelScope.launch(backgroundDispatcher) {
       if (fingerprintManagerInteractor.removeFingerprint(fp)) {
         updateEnrolledFingerprints()
@@ -250,7 +250,7 @@
   }
 
   /** A request to rename a fingerprint */
-  fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+  fun renameFingerprint(fp: FingerprintData, newName: String) {
     viewModelScope.launch {
       fingerprintManagerInteractor.renameFingerprint(fp, newName)
       updateEnrolledFingerprints()
@@ -261,12 +261,12 @@
     _attemptsSoFar.update { it + 1 }
   }
 
-  private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
+  private suspend fun onAuthSuccess(success: FingerprintAuthAttemptModel.Success) {
     _authSucceeded.emit(success)
     _attemptsSoFar.update { 0 }
   }
 
-  private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
+  private fun lockout(attemptViewModel: FingerprintAuthAttemptModel.Error) {
     _isLockedOut.update { attemptViewModel }
   }
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
index 4c33f7f..181da4e 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/PreferenceViewModel.kt
@@ -16,15 +16,15 @@
 
 package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel
 
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 
 /** Classed use to represent a Dialogs state. */
 sealed class PreferenceViewModel {
   data class RenameDialog(
-    val fingerprintViewModel: FingerprintViewModel,
+      val fingerprintViewModel: FingerprintData,
   ) : PreferenceViewModel()
 
   data class DeleteDialog(
-    val fingerprintViewModel: FingerprintViewModel,
+      val fingerprintViewModel: FingerprintData,
   ) : PreferenceViewModel()
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
new file mode 100644
index 0000000..387ab7e
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -0,0 +1,64 @@
+/*
+ * 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.connecteddevice.audiosharing;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.widget.ValidatedEditTextPreference;
+
+public class AudioSharingNamePreference extends ValidatedEditTextPreference {
+    private static final String TAG = "AudioSharingNamePreference";
+
+    public AudioSharingNamePreference(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initialize();
+    }
+
+    public AudioSharingNamePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize();
+    }
+
+    public AudioSharingNamePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize();
+    }
+
+    public AudioSharingNamePreference(Context context) {
+        super(context);
+        initialize();
+    }
+
+    private void initialize() {
+        setLayoutResource(
+                com.android.settingslib.widget.preference.twotarget.R.layout.preference_two_target);
+        setWidgetLayoutResource(R.layout.preference_widget_qrcode);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        final ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
+        shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
new file mode 100644
index 0000000..18c9bfd
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.connecteddevice.audiosharing;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
+import com.android.settings.widget.ValidatedEditTextPreference;
+
+public class AudioSharingNamePreferenceController extends BasePreferenceController
+        implements ValidatedEditTextPreference.Validator,
+                Preference.OnPreferenceChangeListener,
+                DefaultLifecycleObserver {
+
+    private static final String TAG = "AudioSharingNamePreferenceController";
+
+    private static final String PREF_KEY = "audio_sharing_stream_name";
+
+    protected Preference mPreference;
+
+    private AudioSharingNameTextValidator mAudioSharingNameTextValidator;
+
+    public AudioSharingNamePreferenceController(Context context) {
+        super(context, PREF_KEY);
+        mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREF_KEY;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        // TODO: update broadcast when name is changed.
+        return true;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
+    }
+
+    @Override
+    public boolean isTextValid(String value) {
+        return mAudioSharingNameTextValidator.isTextValid(value);
+    }
+
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        // TODO
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        // TODO
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
similarity index 60%
copy from src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
copy to src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
index db28e79..9492961 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
@@ -14,21 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.settings.biometrics.fingerprint2.shared.model
+package com.android.settings.connecteddevice.audiosharing;
 
-data class FingerprintViewModel(
-  val name: String,
-  val fingerId: Int,
-  val deviceId: Long,
-)
+import com.android.settings.widget.ValidatedEditTextPreference;
 
-sealed class FingerprintAuthAttemptViewModel {
-  data class Success(
-    val fingerId: Int,
-  ) : FingerprintAuthAttemptViewModel()
-
-  data class Error(
-    val error: Int,
-    val message: String,
-  ) : FingerprintAuthAttemptViewModel()
+public class AudioSharingNameTextValidator implements ValidatedEditTextPreference.Validator {
+    @Override
+    public boolean isTextValid(String value) {
+        // TODO: Add validate rule if applicable.
+        return true;
+    }
 }
diff --git a/src/com/android/settings/datausage/CellDataPreference.java b/src/com/android/settings/datausage/CellDataPreference.java
index 9374217..3bd3ecc 100644
--- a/src/com/android/settings/datausage/CellDataPreference.java
+++ b/src/com/android/settings/datausage/CellDataPreference.java
@@ -26,11 +26,10 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
-import android.widget.Checkable;
+import android.widget.CompoundButton;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.AlertDialog.Builder;
-import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
@@ -51,12 +50,10 @@
     public int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     public boolean mChecked;
     public boolean mMultiSimDialog;
-    private MobileDataEnabledListener mDataStateListener;
+    private final MobileDataEnabledListener mDataStateListener;
 
     public CellDataPreference(Context context, AttributeSet attrs) {
-        super(context, attrs, TypedArrayUtils.getAttr(context,
-                androidx.preference.R.attr.switchPreferenceStyle,
-                android.R.attr.switchPreferenceStyle));
+        super(context, attrs, androidx.preference.R.attr.switchPreferenceCompatStyle);
         mDataStateListener = new MobileDataEnabledListener(context, this);
     }
 
@@ -170,9 +167,10 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-        final View switchView = holder.findViewById(android.R.id.switch_widget);
+        final CompoundButton switchView =
+                (CompoundButton) holder.findViewById(androidx.preference.R.id.switchWidget);
         switchView.setClickable(false);
-        ((Checkable) switchView).setChecked(mChecked);
+        switchView.setChecked(mChecked);
     }
 
     @Override
diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java
index e5a7307..e8e2109 100644
--- a/src/com/android/settings/datausage/ChartDataUsagePreference.java
+++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java
@@ -112,9 +112,6 @@
             // increment by current bucket total
             totalData += data.getUsage();
 
-            if (points.size() == 1) {
-                points.put(toInt(startTime - mStart) - 1, -1);
-            }
             points.put(toInt(startTime - mStart + 1), (int) (totalData / RESOLUTION));
             points.put(toInt(endTime - mStart), (int) (totalData / RESOLUTION));
         }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
index 9d7b629..a7deaf9 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
@@ -45,6 +45,9 @@
     // Caches app label and icon to improve loading performance.
     static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();
 
+    // Caches package name and uid to improve loading performance.
+    static final Map<String, Integer> sPackageNameAndUidCache = new HashMap<>();
+
     // Whether a specific item is valid to launch restriction page?
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
@@ -289,10 +292,20 @@
             return false;
         }
 
-        final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
+        final int uid = getPackageUid(packageName);
         return uid == BatteryUtils.UID_REMOVED_APPS || uid == BatteryUtils.UID_NULL;
     }
 
+    private int getPackageUid(String packageName) {
+        if (sPackageNameAndUidCache.containsKey(packageName)) {
+            return sPackageNameAndUidCache.get(packageName);
+        }
+
+        int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
+        sPackageNameAndUidCache.put(packageName, uid);
+        return uid;
+    }
+
     void loadLabelAndIcon() {
         if (mIsLoaded) {
             return;
@@ -498,10 +511,11 @@
         return builder.toString();
     }
 
-    /** Clears app icon and label cache data. */
+    /** Clears all cache data. */
     public static void clearCache() {
         sResourceCache.clear();
         sValidForRestriction.clear();
+        sPackageNameAndUidCache.clear();
     }
 
     private Drawable getBadgeIconForUser(Drawable icon) {
diff --git a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
new file mode 100644
index 0000000..be72e56
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
@@ -0,0 +1,85 @@
+/*
+ * 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.fuelgauge.datasaver;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.NetworkPolicyManager;
+
+import androidx.annotation.VisibleForTesting;
+
+/** A class to dynamically manage per apps {@link NetworkPolicyManager} POLICY_ flags. */
+public final class DynamicDenylistManager {
+
+    private static final String TAG = "DynamicDenylistManager";
+    private static final String PREF_KEY_MANUAL_DENY = "manual_denylist_preference";
+    private static final String PREF_KEY_DYNAMIC_DENY = "dynamic_denylist_preference";
+
+    private final Context mContext;
+    private final NetworkPolicyManager mNetworkPolicyManager;
+
+    private static DynamicDenylistManager sInstance;
+
+    /** @return a DynamicDenylistManager object */
+    public static DynamicDenylistManager getInstance(Context context) {
+        synchronized (DynamicDenylistManager.class) {
+            if (sInstance == null) {
+                sInstance = new DynamicDenylistManager(context);
+            }
+            return sInstance;
+        }
+    }
+
+    DynamicDenylistManager(Context context) {
+        mContext = context.getApplicationContext();
+        mNetworkPolicyManager = NetworkPolicyManager.from(mContext);
+    }
+
+    /** Update the target uid policy in {@link #getManualDenylistPref()}. */
+    public void updateManualDenylist(String uid, int policy) {
+        if (policy != NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) {
+            getManualDenylistPref().edit().remove(uid).apply();
+        } else {
+            getManualDenylistPref().edit().putInt(uid, policy).apply();
+        }
+    }
+
+    /** Return true if the target uid is in {@link #getManualDenylistPref()}. */
+    public boolean isInManualDenylist(String uid) {
+        return getManualDenylistPref().contains(uid);
+    }
+
+    /** Clear all data in {@link #getManualDenylistPref()} */
+    public void clearManualDenylistPref() {
+        getManualDenylistPref().edit().clear().apply();
+    }
+
+    /** Clear all data in {@link #getDynamicDenylistPref()} */
+    public void clearDynamicDenylistPref() {
+        getDynamicDenylistPref().edit().clear().apply();
+    }
+
+    @VisibleForTesting
+    SharedPreferences getManualDenylistPref() {
+        return mContext.getSharedPreferences(PREF_KEY_MANUAL_DENY, Context.MODE_PRIVATE);
+    }
+
+    @VisibleForTesting
+    SharedPreferences getDynamicDenylistPref() {
+        return mContext.getSharedPreferences(PREF_KEY_DYNAMIC_DENY, Context.MODE_PRIVATE);
+    }
+}
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index bfe0749..43fc9cf 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.localepicker;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.os.Bundle;
@@ -38,6 +39,7 @@
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.LocaleStore;
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.shortcut.ShortcutsUpdateTask;
 
 import java.text.NumberFormat;
@@ -225,6 +227,12 @@
                     "Negative position in onItemMove %d -> %d", fromPosition, toPosition));
         }
 
+        if (fromPosition != toPosition) {
+            FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+                    .action(mContext, SettingsEnums.ACTION_REORDER_LANGUAGE,
+                            mDragLocale.getLocale().toLanguageTag() + " move to " + toPosition);
+        }
+
         notifyItemChanged(fromPosition); // to update the numbers
         notifyItemChanged(toPosition);
         notifyItemMoved(fromPosition, toPosition);
@@ -263,6 +271,9 @@
         for (int i = itemCount - 1; i >= 0; i--) {
             localeInfo = mFeedItemList.get(i);
             if (localeInfo.getChecked()) {
+                FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+                        .action(mContext, SettingsEnums.ACTION_REMOVE_LANGUAGE,
+                                localeInfo.getLocale().toLanguageTag());
                 mFeedItemList.remove(i);
             }
         }
diff --git a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
index a639c9d..b962b9e 100644
--- a/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
+++ b/src/com/android/settings/localepicker/LocaleHelperPreferenceController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.localepicker;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
@@ -24,8 +25,10 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.HelpUtils;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.widget.FooterPreference;
 
 /**
@@ -36,8 +39,11 @@
 
     private static final String KEY_FOOTER_LANGUAGE_PICKER = "footer_languages_picker";
 
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+
     public LocaleHelperPreferenceController(Context context) {
         super(context);
+        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
     }
 
     @Override
@@ -72,6 +78,7 @@
                 mContext.getString(R.string.link_locale_picker_footer_learn_more),
                 mContext.getClass().getName());
         if (intent != null) {
+            mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_LANGUAGES_LEARN_MORE);
             mContext.startActivity(intent);
         } else {
             Log.w(TAG, "HelpIntent is null");
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index bdda549..28f066a 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -224,6 +224,8 @@
             localeInfo = mayAppendUnicodeTags(localeInfo, preferencesTags);
             mAdapter.addLocale(localeInfo);
             updateVisibilityOfRemoveMenu();
+            mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_ADD_LANGUAGE,
+                    localeInfo.getLocale().toLanguageTag());
         } else if (requestCode == DIALOG_CONFIRM_SYSTEM_DEFAULT) {
             localeInfo = mAdapter.getFeedItemList().get(0);
             if (resultCode == Activity.RESULT_OK) {
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.java b/src/com/android/settings/network/MobileNetworkListFragment.java
deleted file mode 100644
index 3de05af..0000000
--- a/src/com/android/settings/network/MobileNetworkListFragment.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 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.network;
-
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.UserManager;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.network.telephony.MobileNetworkUtils;
-import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.search.SearchIndexable;
-
-@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class MobileNetworkListFragment extends DashboardFragment {
-    private static final String LOG_TAG = "NetworkListFragment";
-
-    private static final String KEY_ADD_SIM = "add_sim";
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        // Disable the animation of the preference list
-        final RecyclerView prefListView = getListView();
-        if (prefListView != null) {
-            prefListView.setItemAnimator(null);
-        }
-
-        findPreference(KEY_ADD_SIM).setVisible(MobileNetworkUtils.showEuiccSettings(getContext()));
-    }
-
-    @Override
-    protected int getPreferenceScreenResId() {
-        return R.xml.network_provider_sims_list;
-    }
-
-    @Override
-    protected String getLogTag() {
-        return LOG_TAG;
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return SettingsEnums.MOBILE_NETWORK_LIST;
-    }
-
-    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
-
-                @Override
-                protected boolean isPageSearchEnabled(Context context) {
-                    return SubscriptionUtil.isSimHardwareVisible(context) &&
-                            context.getSystemService(UserManager.class).isAdminUser();
-                }
-            };
-}
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.kt b/src/com/android/settings/network/MobileNetworkListFragment.kt
new file mode 100644
index 0000000..5000afd
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkListFragment.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.network
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.os.Bundle
+import android.provider.Settings
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import com.android.settings.R
+import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.network.telephony.MobileNetworkUtils
+import com.android.settings.search.BaseSearchIndexProvider
+import com.android.settings.utils.observeSettingsGlobalBoolean
+import com.android.settingslib.search.SearchIndexable
+import com.android.settingslib.spaprivileged.framework.common.userManager
+
+@SearchIndexable(forTarget = SearchIndexable.ALL and SearchIndexable.ARC.inv())
+class MobileNetworkListFragment : DashboardFragment() {
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        observeAirplaneModeAndFinishIfOn()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        // Disable the animation of the preference list
+        listView.itemAnimator = null
+
+        findPreference<Preference>(KEY_ADD_SIM)!!.isVisible =
+            MobileNetworkUtils.showEuiccSettings(context)
+    }
+
+    override fun getPreferenceScreenResId() = R.xml.network_provider_sims_list
+
+    override fun getLogTag() = LOG_TAG
+
+    override fun getMetricsCategory() = SettingsEnums.MOBILE_NETWORK_LIST
+
+    companion object {
+        private const val LOG_TAG = "NetworkListFragment"
+        private const val KEY_ADD_SIM = "add_sim"
+
+        @JvmStatic
+        fun SettingsPreferenceFragment.observeAirplaneModeAndFinishIfOn() {
+            requireContext().observeSettingsGlobalBoolean(
+                name = Settings.Global.AIRPLANE_MODE_ON,
+                lifecycle = viewLifecycleOwner.lifecycle,
+            ) { isAirplaneModeOn: Boolean ->
+                if (isAirplaneModeOn) {
+                    finish()
+                }
+            }
+        }
+
+        @JvmField
+        val SEARCH_INDEX_DATA_PROVIDER = SearchIndexProvider()
+
+        @VisibleForTesting
+        class SearchIndexProvider : BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
+            public override fun isPageSearchEnabled(context: Context): Boolean =
+                SubscriptionUtil.isSimHardwareVisible(context) &&
+                    context.userManager.isAdminUser
+        }
+    }
+}
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 0e23a0e..81da0bf 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -96,7 +96,7 @@
         getNetworkTypeSelectedOptionsState(apnData.networkType)
     }
     RegularScaffold(
-        title = stringResource(id = R.string.apn_edit),
+        title = if(apnDataInit.newApn) stringResource(id = R.string.apn_add) else stringResource(id = R.string.apn_edit),
         actions = {
             IconButton(onClick = {
                 validateAndSaveApnData(
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index afc1b7e..7e290b8 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.network.telephony;
 
+import static com.android.settings.network.MobileNetworkListFragment.observeAirplaneModeAndFinishIfOn;
+
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -31,7 +33,10 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.View;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
@@ -327,6 +332,12 @@
     }
 
     @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        observeAirplaneModeAndFinishIfOn(this);
+    }
+
+    @Override
     public void onResume() {
         super.onResume();
         mMobileNetworkRepository.addRegister(this, this, mSubId);
@@ -361,11 +372,6 @@
         super.onPause();
     }
 
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-    }
-
     @VisibleForTesting
     void onRestoreInstance(Bundle icicle) {
         if (icicle != null) {
diff --git a/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java b/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java
index 03a59de..d509d2e 100644
--- a/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java
+++ b/src/com/android/settings/regionalpreferences/FirstDayOfWeekItemListController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.regionalpreferences;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 
 import com.android.settings.R;
@@ -57,4 +58,9 @@
     protected String[] getUnitValues() {
         return mContext.getResources().getStringArray(R.array.first_day_of_week);
     }
+
+    @Override
+    protected int getMetricsActionKey() {
+        return SettingsEnums.ACTION_SET_FIRST_DAY_OF_WEEK;
+    }
 }
diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
index 2f2bf76..ac0e7ee 100644
--- a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
+++ b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
@@ -22,16 +22,20 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.TickButtonPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 /** A base controller for handling all regional preferences controllers. */
 public abstract class RegionalPreferenceListBasePreferenceController extends
         BasePreferenceController {
 
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
     private PreferenceCategory mPreferenceCategory;
 
     public RegionalPreferenceListBasePreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
+        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
     }
 
     @Override
@@ -61,6 +65,8 @@
                 RegionalPreferencesDataUtils.savePreference(mContext, getExtensionTypes(),
                         item.equals(RegionalPreferencesDataUtils.DEFAULT_VALUE)
                                 ? null : item);
+                mMetricsFeatureProvider.action(mContext, getMetricsActionKey(),
+                        getPreferenceTitle(value) + " > " +  getPreferenceTitle(item));
                 return true;
             });
             pref.setSelected(!value.isEmpty() && item.equals(value));
@@ -90,4 +96,8 @@
     protected abstract String getExtensionTypes();
 
     protected abstract String[] getUnitValues();
+
+    protected abstract int getMetricsActionKey();
+
+
 }
diff --git a/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java b/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java
index c51ca71..91ab1a2 100644
--- a/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java
+++ b/src/com/android/settings/regionalpreferences/TemperatureUnitListController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.regionalpreferences;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 
 import com.android.settings.R;
@@ -55,4 +56,9 @@
     protected String[] getUnitValues() {
         return mContext.getResources().getStringArray(R.array.temperature_units);
     }
+
+    @Override
+    protected int getMetricsActionKey() {
+        return SettingsEnums.ACTION_SET_TEMPERATURE_UNIT;
+    }
 }
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index 3200b81..307ff11 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -17,6 +17,8 @@
 package com.android.settings.spa.app.appinfo
 
 import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -25,16 +27,22 @@
 import com.android.settingslib.spa.widget.button.ActionButtons
 
 @Composable
-fun AppButtons(packageInfoPresenter: PackageInfoPresenter) {
+/**
+ * @param featureFlags can be overridden in tests
+ */
+fun AppButtons(packageInfoPresenter: PackageInfoPresenter, featureFlags: FeatureFlags = FeatureFlagsImpl()) {
     if (remember(packageInfoPresenter) { packageInfoPresenter.isMainlineModule() }) return
-    val presenter = remember { AppButtonsPresenter(packageInfoPresenter) }
+    val presenter = remember { AppButtonsPresenter(packageInfoPresenter, featureFlags) }
     ActionButtons(actionButtons = presenter.getActionButtons())
 }
 
 private fun PackageInfoPresenter.isMainlineModule(): Boolean =
     AppUtils.isMainlineModule(userPackageManager, packageName)
 
-private class AppButtonsPresenter(private val packageInfoPresenter: PackageInfoPresenter) {
+private class AppButtonsPresenter(
+    private val packageInfoPresenter: PackageInfoPresenter,
+    private val featureFlags: FeatureFlags
+) {
     private val appLaunchButton = AppLaunchButton(packageInfoPresenter)
     private val appInstallButton = AppInstallButton(packageInfoPresenter)
     private val appDisableButton = AppDisableButton(packageInfoPresenter)
@@ -50,7 +58,7 @@
 
     @Composable
     private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
-        appLaunchButton.getActionButton(app),
+        if (featureFlags.archiving()) null else appLaunchButton.getActionButton(app),
         appInstallButton.getActionButton(app),
         appDisableButton.getActionButton(app),
         appUninstallButton.getActionButton(app),
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index a9d16ae..ed912c3 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -18,6 +18,8 @@
 
 import android.app.settings.SettingsEnums
 import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
 import android.os.Bundle
 import android.os.UserHandle
 import android.util.FeatureFlagUtils
@@ -119,9 +121,11 @@
     LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
     val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
     val app = checkNotNull(packageInfo.applicationInfo)
+    val featureFlags: FeatureFlags = FeatureFlagsImpl()
     RegularScaffold(
         title = stringResource(R.string.application_info_label),
         actions = {
+            if (featureFlags.archiving()) TopBarAppLaunchButton(packageInfoPresenter, app)
             AppInfoSettingsMoreOptions(packageInfoPresenter, app)
         }
     ) {
diff --git a/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButton.kt b/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButton.kt
new file mode 100644
index 0000000..92ad139
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButton.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.model.app.userHandle
+
+@Composable
+fun TopBarAppLaunchButton(packageInfoPresenter: PackageInfoPresenter, app: ApplicationInfo) {
+    val intent = packageInfoPresenter.launchIntent(app = app) ?: return
+    IconButton({ launchButtonAction(intent, app, packageInfoPresenter) }) {
+        Icon(
+            imageVector = Icons.AutoMirrored.Outlined.Launch,
+            contentDescription = stringResource(R.string.launch_instant_app),
+        )
+    }
+}
+
+private fun PackageInfoPresenter.launchIntent(
+    app: ApplicationInfo
+): Intent? {
+    return userPackageManager.getLaunchIntentForPackage(app.packageName)
+}
+
+private fun launchButtonAction(
+    intent: Intent,
+    app: ApplicationInfo,
+    packageInfoPresenter: PackageInfoPresenter
+) {
+    try {
+        packageInfoPresenter.context.startActivityAsUser(intent, app.userHandle)
+    } catch (_: ActivityNotFoundException) {
+        // Only happens after package changes like uninstall, and before page auto refresh or
+        // close, so ignore this exception is safe.
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt b/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt
new file mode 100644
index 0000000..fdfbdb4
--- /dev/null
+++ b/src/com/android/settings/utils/SettingsGlobalBooleanDelegate.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.utils
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+fun Context.observeSettingsGlobalBoolean(
+    name: String,
+    lifecycle: Lifecycle,
+    onChange: (newValue: Boolean) -> Unit,
+) {
+    val field by settingsGlobalBoolean(name)
+    val contentObserver = object : ContentObserver(Handler.getMain()) {
+        override fun onChange(selfChange: Boolean) {
+            onChange(field)
+        }
+    }
+    val uri = Settings.Global.getUriFor(name)
+    lifecycle.addObserver(object : DefaultLifecycleObserver {
+        override fun onStart(owner: LifecycleOwner) {
+            contentResolver.registerContentObserver(uri, false, contentObserver)
+            onChange(field)
+        }
+
+        override fun onStop(owner: LifecycleOwner) {
+            contentResolver.unregisterContentObserver(contentObserver)
+        }
+    })
+}
+
+fun Context.settingsGlobalBoolean(name: String): ReadWriteProperty<Any?, Boolean> =
+    SettingsGlobalBooleanDelegate(this, name)
+
+private class SettingsGlobalBooleanDelegate(context: Context, private val name: String) :
+    ReadWriteProperty<Any?, Boolean> {
+
+    private val contentResolver: ContentResolver = context.contentResolver
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean =
+        Settings.Global.getInt(contentResolver, name, 0) != 0
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
+        Settings.Global.putInt(contentResolver, name, if (value) 1 else 0)
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt b/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
index cea6676..024f346 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint2/fragment/FingerprintEnrollIntroFragmentTest.kt
@@ -33,12 +33,15 @@
 import androidx.test.espresso.matcher.ViewMatchers.withText
 import androidx.test.runner.AndroidJUnit4
 import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NavState
 import com.android.settings.testutils2.FakeFingerprintManagerInteractor
 import com.google.android.setupdesign.GlifLayout
 import com.google.android.setupdesign.template.RequireScrollMixin
@@ -65,9 +68,12 @@
       backgroundDispatcher,
       interactor,
       gatekeeperViewModel,
-      canSkipConfirm = true,
+      Intro,
+      NavState(true),
+      Default,
     )
-  private var fingerprintViewModel = FingerprintEnrollViewModel(interactor, backgroundDispatcher)
+  private var fingerprintViewModel =
+    FingerprintEnrollViewModel(interactor, gatekeeperViewModel, navigationViewModel)
   private var fingerprintScrollViewModel = FingerprintScrollViewModel()
 
   @Before
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
index f43feec..bbba294 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
@@ -352,16 +352,18 @@
     }
 
     @Test
-    public void testClearCache_clearDataForResourcesAndFlags() {
+    public void testClearCache_clearDataForAllCaches() {
         BatteryDiffEntry.sResourceCache.put(
                 "fake application key",
                 new BatteryEntry.NameAndIcon("app label", null, /* iconId= */ 0));
         BatteryDiffEntry.sValidForRestriction.put("fake application key", Boolean.valueOf(false));
+        BatteryDiffEntry.sPackageNameAndUidCache.put(PACKAGE_NAME, UID);
 
         BatteryDiffEntry.clearCache();
 
         assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
         assertThat(BatteryDiffEntry.sValidForRestriction).isEmpty();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache).isEmpty();
     }
 
     @Test
@@ -445,7 +447,11 @@
         final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
 
         assertThat(entry.isSystemEntry()).isFalse();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isFalse();
         assertThat(entry.isUninstalledEntry()).isFalse();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isTrue();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.get(PACKAGE_NAME)).isEqualTo(UID);
+
     }
 
     @Test
@@ -457,7 +463,9 @@
         final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
 
         assertThat(entry.isSystemEntry()).isFalse();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isFalse();
         assertThat(entry.isUninstalledEntry()).isFalse();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(PACKAGE_NAME)).isFalse();
     }
 
     @Test
@@ -469,7 +477,11 @@
         final BatteryDiffEntry entry = createBatteryDiffEntry(10, new BatteryHistEntry(values));
 
         assertThat(entry.isSystemEntry()).isFalse();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.containsKey(UNINSTALLED_PACKAGE_NAME))
+                .isFalse();
         assertThat(entry.isUninstalledEntry()).isTrue();
+        assertThat(BatteryDiffEntry.sPackageNameAndUidCache.get(UNINSTALLED_PACKAGE_NAME))
+                .isEqualTo(BatteryUtils.UID_NULL);
     }
 
     @Test
@@ -590,7 +602,7 @@
         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
         doReturn(drawable).when(mMockPackageManager).getDefaultActivityIcon();
         doReturn(null).when(mMockPackageManager).getApplicationInfo("com.a.b.c", 0);
-        doReturn(new String[] {"com.a.b.c"}).when(mMockPackageManager).getPackagesForUid(1001);
+        doReturn(new String[]{"com.a.b.c"}).when(mMockPackageManager).getPackagesForUid(1001);
         return createBatteryDiffEntry(10, batteryHistEntry);
     }
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
new file mode 100644
index 0000000..cdf1514
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.fuelgauge.datasaver;
+
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class DynamicDenylistManagerTest {
+
+    private static final String FAKE_UID_1 = "package_uid_1";
+    private static final String FAKE_UID_2 = "package_uid_2";
+
+    private SharedPreferences mManualDenyListPref;
+    private SharedPreferences mDynamicDenyListPref;
+    private DynamicDenylistManager mDynamicDenylistManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application.getApplicationContext();
+        mDynamicDenylistManager = new DynamicDenylistManager(mContext);
+        mManualDenyListPref = mDynamicDenylistManager.getManualDenylistPref();
+        mDynamicDenyListPref = mDynamicDenylistManager.getDynamicDenylistPref();
+    }
+
+    @After
+    public void tearDown() {
+        mDynamicDenylistManager.clearManualDenylistPref();
+        mDynamicDenylistManager.clearDynamicDenylistPref();
+    }
+
+    @Test
+    public void getManualDenylistPref_isEmpty() {
+        assertThat(mManualDenyListPref.getAll()).isEmpty();
+    }
+
+    @Test
+    public void getDynamicDenylistPref_isEmpty() {
+        assertThat(mDynamicDenyListPref.getAll()).isEmpty();
+    }
+
+    @Test
+    public void getManualDenylistPref_initiated_containsExpectedValue() {
+        mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+        assertThat(mManualDenyListPref.getAll().size()).isEqualTo(1);
+        assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+    }
+
+    @Test
+    public void getDynamicDenylistPref_initiated_containsExpectedValue() {
+        mDynamicDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+        assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+        assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
+    }
+
+    @Test
+    public void updateManualDenylist_policyReject_addsUid() {
+        mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND);
+
+        assertThat(mManualDenyListPref.getAll()).hasSize(1);
+        assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+    }
+
+    @Test
+    public void updateManualDenylist_policyNone_removesUid() {
+        mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+        assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+
+        mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_NONE);
+
+        assertThat(mManualDenyListPref.getAll()).isEmpty();
+    }
+
+    @Test
+    public void updateManualDenylist_samePolicy_doNothing() {
+        mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+        assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+
+        mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND);
+
+        assertThat(mManualDenyListPref.getAll()).hasSize(1);
+    }
+
+    @Test
+    public void isManualDenylist_returnsFalse() {
+        assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+    }
+
+    @Test
+    public void isManualDenylist_incorrectUid_returnsFalse() {
+        mManualDenyListPref.edit().putInt(FAKE_UID_2, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+        assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+    }
+
+    @Test
+    public void isManualDenylist_initiated_returnsTrue() {
+        mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+
+        assertTrue(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+    }
+
+    @Test
+    public void clearManualDenylistPref_isEmpty() {
+        mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+        assertThat(mManualDenyListPref.getAll()).hasSize(1);
+        assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+
+        mDynamicDenylistManager.clearManualDenylistPref();
+
+        assertThat(mManualDenyListPref.getAll()).isEmpty();
+    }
+
+    @Test
+    public void clearDynamicDenylistPref_isEmpty() {
+        mDynamicDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+        assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+        assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
+
+        mDynamicDenylistManager.clearDynamicDenylistPref();
+
+        assertThat(mDynamicDenyListPref.getAll()).isEmpty();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java
deleted file mode 100644
index 2e04ea7..0000000
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkListFragmentTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 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.network;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.UserManager;
-
-import com.android.settings.R;
-import com.android.settings.search.BaseSearchIndexProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.util.ReflectionHelpers;
-
-@RunWith(RobolectricTestRunner.class)
-public class MobileNetworkListFragmentTest {
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private UserManager mUserManager;
-
-    private MobileNetworkListFragment mFragment;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mFragment = new MobileNetworkListFragment();
-    }
-
-    @Test
-    public void isPageSearchEnabled_adminUser_shouldReturnTrue() {
-        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
-        when(mUserManager.isAdminUser()).thenReturn(true);
-        final BaseSearchIndexProvider provider =
-                (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER;
-
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
-
-        final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled",
-                ReflectionHelpers.ClassParameter.from(Context.class, mContext));
-        final boolean isEnabled = (Boolean) obj;
-
-        assertThat(isEnabled).isTrue();
-    }
-
-    @Test
-    public void isPageSearchEnabled_nonAdminUser_shouldReturnFalse() {
-        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
-        when(mUserManager.isAdminUser()).thenReturn(false);
-        final BaseSearchIndexProvider provider =
-                (BaseSearchIndexProvider) mFragment.SEARCH_INDEX_DATA_PROVIDER;
-
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true);
-
-        final Object obj = ReflectionHelpers.callInstanceMethod(provider, "isPageSearchEnabled",
-                ReflectionHelpers.ClassParameter.from(Context.class, mContext));
-        final boolean isEnabled = (Boolean) obj;
-
-        assertThat(isEnabled).isFalse();
-    }
-}
diff --git a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
index ad943f2..dd8658c 100644
--- a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
+++ b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
@@ -18,9 +18,9 @@
 
 import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
 import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.systemui.biometrics.shared.model.FingerprintSensor
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
@@ -32,10 +32,11 @@
 class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
 
   var enrollableFingerprints: Int = 5
-  var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
+  var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
   var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
-  var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
-  val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
+  var authenticateAttempt = FingerprintAuthAttemptModel.Success(1)
+  var enrollStateViewModel: List<FingerEnrollState> =
+    listOf(FingerEnrollState.EnrollProgress(5, 5))
   var pressToAuthEnabled = true
 
   var sensorProp =
@@ -46,7 +47,7 @@
       FingerprintSensorType.POWER_BUTTON
     )
 
-  override suspend fun authenticate(): FingerprintAuthAttemptViewModel {
+  override suspend fun authenticate(): FingerprintAuthAttemptModel {
     return authenticateAttempt
   }
 
@@ -54,7 +55,7 @@
     return challengeToGenerate
   }
 
-  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+  override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
     emit(enrolledFingerprintsInternal)
   }
 
@@ -62,24 +63,22 @@
     emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
   }
 
-  override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow {
-    emit(sensorProp)
-  }
+  override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
 
   override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
 
   override suspend fun enroll(
     hardwareAuthToken: ByteArray?,
     enrollReason: EnrollReason
-  ): Flow<FingerEnrollStateViewModel> = flowOf(enrollStateViewModel)
+  ): Flow<FingerEnrollState> = flowOf(*enrollStateViewModel.toTypedArray())
 
-  override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
+  override suspend fun removeFingerprint(fp: FingerprintData): Boolean {
     return enrolledFingerprintsInternal.remove(fp)
   }
 
-  override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+  override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
     if (enrolledFingerprintsInternal.remove(fp)) {
-      enrolledFingerprintsInternal.add(FingerprintViewModel(newName, fp.fingerId, fp.deviceId))
+      enrolledFingerprintsInternal.add(FingerprintData(newName, fp.fingerId, fp.deviceId))
     }
   }
 
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
new file mode 100644
index 0000000..3ba4bac
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class MobileNetworkListFragmentTest {
+    private val mockUserManager = mock<UserManager>()
+
+    private val mockResources = mock<Resources>()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { userManager } doReturn mockUserManager
+        on { resources } doReturn mockResources
+    }
+
+    @Test
+    fun isPageSearchEnabled_adminUser_shouldReturnTrue() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn true
+        }
+        mockResources.stub {
+            on { getBoolean(R.bool.config_show_sim_info) } doReturn true
+        }
+
+        val isEnabled =
+            MobileNetworkListFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(context)
+
+        assertThat(isEnabled).isTrue()
+    }
+
+    @Test
+    fun isPageSearchEnabled_nonAdminUser_shouldReturnFalse() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn false
+        }
+        mockResources.stub {
+            on { getBoolean(R.bool.config_show_sim_info) } doReturn true
+        }
+
+        val isEnabled =
+            MobileNetworkListFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(context)
+
+        assertThat(isEnabled).isFalse()
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index 8faf5c9..e2f55ef 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -17,16 +17,21 @@
 package com.android.settings.spa.app.appinfo
 
 import android.content.Context
+import android.content.Intent
 import android.content.pm.ApplicationInfo
+import android.content.pm.FakeFeatureFlagsImpl
+import android.content.pm.Flags
 import android.content.pm.PackageInfo
 import android.content.pm.PackageManager
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
 import com.android.settingslib.applications.AppUtils
 import com.android.settingslib.spa.testutils.delay
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -57,6 +62,8 @@
     @Mock
     private lateinit var packageManager: PackageManager
 
+    private val featureFlags = FakeFeatureFlagsImpl()
+
     @Before
     fun setUp() {
         mockSession = ExtendedMockito.mockitoSession()
@@ -69,6 +76,7 @@
         whenever(packageInfoPresenter.userPackageManager).thenReturn(packageManager)
         whenever(packageManager.getPackageInfo(PACKAGE_NAME, 0)).thenReturn(PACKAGE_INFO)
         whenever(AppUtils.isMainlineModule(packageManager, PACKAGE_NAME)).thenReturn(false)
+        featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
     }
 
     @After
@@ -92,10 +100,28 @@
         composeTestRule.onRoot().assertIsDisplayed()
     }
 
+    @Test
+    fun launchButton_displayed_archivingDisabled() {
+        whenever(packageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(Intent())
+        featureFlags.setFlag(Flags.FLAG_ARCHIVING, false)
+        setContent()
+
+        composeTestRule.onNodeWithText(context.getString(R.string.launch_instant_app)).assertIsDisplayed()
+    }
+
+    @Test
+    fun launchButton_notDisplayed_archivingEnabled() {
+        whenever(packageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(Intent())
+        featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+        setContent()
+
+        composeTestRule.onNodeWithText(context.getString(R.string.launch_instant_app)).assertIsNotDisplayed()
+    }
+
     private fun setContent() {
         whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(PACKAGE_INFO))
         composeTestRule.setContent {
-            AppButtons(packageInfoPresenter)
+            AppButtons(packageInfoPresenter, featureFlags)
         }
 
         composeTestRule.delay()
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButtonTest.kt
new file mode 100644
index 0000000..7b54247
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/TopBarAppLaunchButtonTest.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settingslib.spa.testutils.waitUntilExists
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class TopBarAppLaunchButtonTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private lateinit var mockSession: MockitoSession
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageInfoPresenter: PackageInfoPresenter
+
+    @Mock
+    private lateinit var userPackageManager: PackageManager
+
+    @Before
+    fun setUp() {
+        mockSession = ExtendedMockito.mockitoSession()
+            .initMocks(this)
+            .strictness(Strictness.LENIENT)
+            .startMocking()
+        whenever(packageInfoPresenter.context).thenReturn(context)
+        whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
+        val intent = Intent()
+        whenever(userPackageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(intent)
+    }
+
+    @After
+    fun tearDown() {
+        mockSession.finishMocking()
+    }
+
+    @Test
+    fun topBarAppLaunchButton_isDisplayed() {
+        val app = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+
+        setContent(app)
+
+        composeTestRule.waitUntilExists(
+            hasContentDescription(context.getString(R.string.launch_instant_app))
+        )
+    }
+
+    @Test
+    fun topBarAppLaunchButton_opensApp() {
+        val app = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+
+        setContent(app)
+        composeTestRule.onNodeWithContentDescription(context.getString(R.string.launch_instant_app))
+            .performClick()
+
+        verify(context).startActivityAsUser(any(), eq(app.userHandle))
+    }
+
+    private fun setContent(app: ApplicationInfo) {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                TopBarAppLaunchButton(packageInfoPresenter, app)
+            }
+        }
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt b/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt
new file mode 100644
index 0000000..75c3685
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/utils/SettingsGlobalBooleanDelegateTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.utils
+
+import android.content.Context
+import android.provider.Settings
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsGlobalBooleanDelegateTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun getValue_setTrue_returnTrue() {
+        Settings.Global.putInt(context.contentResolver, TEST_NAME, 1)
+
+        val value by context.settingsGlobalBoolean(TEST_NAME)
+
+        assertThat(value).isTrue()
+    }
+
+    @Test
+    fun getValue_setFalse_returnFalse() {
+        Settings.Global.putInt(context.contentResolver, TEST_NAME, 0)
+
+        val value by context.settingsGlobalBoolean(TEST_NAME)
+
+        assertThat(value).isFalse()
+    }
+
+    @Test
+    fun setValue_setTrue_returnTrue() {
+        var value by context.settingsGlobalBoolean(TEST_NAME)
+
+        value = true
+
+        assertThat(Settings.Global.getInt(context.contentResolver, TEST_NAME, 0)).isEqualTo(1)
+    }
+
+    @Test
+    fun setValue_setFalse_returnFalse() {
+        var value by context.settingsGlobalBoolean(TEST_NAME)
+
+        value = false
+
+        assertThat(Settings.Global.getInt(context.contentResolver, TEST_NAME, 1)).isEqualTo(0)
+    }
+
+    @Test
+    fun observeSettingsGlobalBoolean_valueNotChanged() {
+        var value by context.settingsGlobalBoolean(TEST_NAME)
+        value = false
+        var newValue: Boolean? = null
+
+        context.observeSettingsGlobalBoolean(TEST_NAME, TestLifecycleOwner().lifecycle) {
+            newValue = it
+        }
+
+        assertThat(newValue).isFalse()
+    }
+
+    @Test
+    fun observeSettingsGlobalBoolean_valueChanged() {
+        var value by context.settingsGlobalBoolean(TEST_NAME)
+        value = false
+        var newValue: Boolean? = null
+
+        context.observeSettingsGlobalBoolean(TEST_NAME, TestLifecycleOwner().lifecycle) {
+            newValue = it
+        }
+        value = true
+
+        assertThat(newValue).isFalse()
+    }
+
+    private companion object {
+        const val TEST_NAME = "test_boolean_delegate"
+    }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
index f0d0a0a..3440d2a 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
@@ -26,12 +26,14 @@
 import android.os.Handler
 import androidx.test.core.app.ApplicationProvider
 import com.android.settings.biometrics.GatekeeperPasswordProvider
-import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
+import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
+import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
 import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settings.password.ChooseLockSettingsHelper
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.cancelAndJoin
@@ -69,7 +71,11 @@
   @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
 
   private var testScope = TestScope(backgroundDispatcher)
-  private var pressToAuthProvider = { true }
+  private var pressToAuthProvider =
+    object : PressToAuthProvider {
+      override val isEnabled: Boolean
+        get() = false
+    }
 
   @Before
   fun setup() {
@@ -80,6 +86,7 @@
         fingerprintManager,
         gateKeeperPasswordProvider,
         pressToAuthProvider,
+        Default,
       )
   }
 
@@ -164,7 +171,7 @@
   @Test
   fun testRemoveFingerprint_succeeds() =
     testScope.runTest {
-      val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
+      val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
       val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
 
       val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
@@ -187,7 +194,7 @@
   @Test
   fun testRemoveFingerprint_fails() =
     testScope.runTest {
-      val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
+      val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
       val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
 
       val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
@@ -214,7 +221,7 @@
   @Test
   fun testRenameFingerprint_succeeds() =
     testScope.runTest {
-      val fingerprintToRename = FingerprintViewModel("Finger 2", 1, 2L)
+      val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
 
       underTest.renameFingerprint(fingerprintToRename, "Woo")
 
@@ -226,7 +233,7 @@
     testScope.runTest {
       val fingerprint = Fingerprint("Woooo", 100, 101L)
 
-      var result: FingerprintAuthAttemptViewModel? = null
+      var result: FingerprintAuthAttemptModel? = null
       val job = launch { result = underTest.authenticate() }
 
       val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -247,13 +254,13 @@
 
       runCurrent()
       job.cancelAndJoin()
-      assertThat(result).isEqualTo(FingerprintAuthAttemptViewModel.Success(fingerprint.biometricId))
+      assertThat(result).isEqualTo(FingerprintAuthAttemptModel.Success(fingerprint.biometricId))
     }
 
   @Test
   fun testAuth_lockout() =
     testScope.runTest {
-      var result: FingerprintAuthAttemptViewModel? = null
+      var result: FingerprintAuthAttemptModel? = null
       val job = launch { result = underTest.authenticate() }
 
       val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -274,7 +281,7 @@
       job.cancelAndJoin()
       assertThat(result)
         .isEqualTo(
-          FingerprintAuthAttemptViewModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
+          FingerprintAuthAttemptModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
         )
     }
 
@@ -282,7 +289,7 @@
   fun testEnroll_progress() =
     testScope.runTest {
       val token = byteArrayOf(5, 3, 2)
-      var result: FingerEnrollStateViewModel? = null
+      var result: FingerEnrollState? = null
       val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
       val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
       runCurrent()
@@ -299,14 +306,14 @@
       runCurrent()
       job.cancelAndJoin()
 
-      assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollProgress(1))
+      assertThat(result).isEqualTo(FingerEnrollState.EnrollProgress(1, 2))
     }
 
   @Test
   fun testEnroll_help() =
     testScope.runTest {
       val token = byteArrayOf(5, 3, 2)
-      var result: FingerEnrollStateViewModel? = null
+      var result: FingerEnrollState? = null
       val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
       val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
       runCurrent()
@@ -323,14 +330,14 @@
       runCurrent()
       job.cancelAndJoin()
 
-      assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollHelp(-1, "help"))
+      assertThat(result).isEqualTo(FingerEnrollState.EnrollHelp(-1, "help"))
     }
 
   @Test
   fun testEnroll_error() =
     testScope.runTest {
       val token = byteArrayOf(5, 3, 2)
-      var result: FingerEnrollStateViewModel? = null
+      var result: FingerEnrollState? = null
       val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
       val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
       runCurrent()
@@ -343,17 +350,20 @@
           capture(enrollCallback),
           eq(FingerprintManager.ENROLL_FIND_SENSOR)
         )
-      enrollCallback.value.onEnrollmentError(-2, "error")
+      enrollCallback.value.onEnrollmentError(-1, "error")
       runCurrent()
       job.cancelAndJoin()
-
-      assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollError(-2, "error"))
+      assertThat(result).isInstanceOf(FingerEnrollState.EnrollError::class.java)
     }
 
   private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
   private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
   private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
   private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
+
   inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
     ArgumentCaptor.forClass(T::class.java)
 }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
index 509b0ed..bd94cba 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
@@ -21,7 +21,9 @@
 import android.view.accessibility.AccessibilityManager
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.test.core.app.ApplicationProvider
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
@@ -70,6 +72,7 @@
   private lateinit var foldStateViewModel: FoldStateViewModel
   private lateinit var orientationStateViewModel: OrientationStateViewModel
   private lateinit var underTest: FingerprintEnrollFindSensorViewModel
+  private lateinit var backgroundViewModel: BackgroundViewModel
   private val context: Context = ApplicationProvider.getApplicationContext()
   private val accessibilityManager: AccessibilityManager =
     context.getSystemService(AccessibilityManager::class.java)!!
@@ -93,12 +96,18 @@
           fakeFingerprintManagerInteractor,
           gatekeeperViewModel,
           canSkipConfirm = true,
+          Default,
         )
         .create(FingerprintEnrollNavigationViewModel::class.java)
+
+    backgroundViewModel =
+      BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
+    backgroundViewModel.inForeground()
     enrollViewModel =
       FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
           fakeFingerprintManagerInteractor,
-          backgroundDispatcher
+          gatekeeperViewModel,
+          navigationViewModel,
         )
         .create(FingerprintEnrollViewModel::class.java)
     accessibilityViewModel =
@@ -114,6 +123,7 @@
           navigationViewModel,
           enrollViewModel,
           gatekeeperViewModel,
+          backgroundViewModel,
           accessibilityViewModel,
           foldStateViewModel,
           orientationStateViewModel
@@ -123,6 +133,7 @@
     // Navigate to Education page
     navigationViewModel.nextStep()
   }
+
   @After
   fun tearDown() {
     Dispatchers.resetMain()
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
new file mode 100644
index 0000000..46e883a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.fingerprint2.ui.enrollment.modules.enrolling.rfps.viewmodel
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class RFPSIconTouchViewModelTest {
+  @JvmField @Rule var rule = MockitoJUnit.rule()
+
+  @get:Rule val instantTaskRule = InstantTaskExecutorRule()
+
+  private var backgroundDispatcher = StandardTestDispatcher()
+  private var testScope = TestScope(backgroundDispatcher)
+  private lateinit var rfpsIconTouchViewModel: RFPSIconTouchViewModel
+
+  @Before
+  fun setup() {
+    Dispatchers.setMain(backgroundDispatcher)
+    testScope = TestScope(backgroundDispatcher)
+    rfpsIconTouchViewModel =
+      RFPSIconTouchViewModel()
+  }
+
+  @After
+  fun tearDown() {
+    Dispatchers.resetMain()
+  }
+
+  @Test
+  fun initShouldNotShowDialog() =
+    testScope.runTest {
+      var shouldShowDialog = false
+
+      val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+      runCurrent()
+
+      assertThat(shouldShowDialog).isFalse()
+      job.cancel()
+    }
+
+  @Test
+  fun shouldShowDialogTest() =
+    testScope.runTest {
+      var shouldShowDialog = false
+
+      val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+      runCurrent()
+
+      assertThat(shouldShowDialog).isTrue()
+      job.cancel()
+    }
+
+  @Test
+  fun stateShouldBeFalseAfterReset() =
+    testScope.runTest {
+      var shouldShowDialog = false
+
+      val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+      runCurrent()
+
+      assertThat(shouldShowDialog).isTrue()
+
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      runCurrent()
+
+      assertThat(shouldShowDialog).isFalse()
+
+      job.cancel()
+    }
+
+  @Test
+  fun toggleMultipleTimes() =
+    testScope.runTest {
+      var shouldShowDialog = false
+
+      val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
+
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+      runCurrent()
+
+      assertThat(shouldShowDialog).isTrue()
+
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      runCurrent()
+
+      assertThat(shouldShowDialog).isFalse()
+
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+      rfpsIconTouchViewModel.userTouchedFingerprintIcon()
+
+      runCurrent()
+      assertThat(shouldShowDialog).isTrue()
+
+      job.cancel()
+    }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
new file mode 100644
index 0000000..efb4a07
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.fingerprint2.ui.enrollment.viewmodel
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.fingerprint2.shared.model.Default
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NavState
+import com.android.settings.testutils2.FakeFingerprintManagerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class FingerprintEnrollEnrollingViewModelTest {
+  @JvmField @Rule var rule = MockitoJUnit.rule()
+
+  @get:Rule val instantTaskRule = InstantTaskExecutorRule()
+
+  private var backgroundDispatcher = StandardTestDispatcher()
+  private lateinit var enrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
+  private lateinit var backgroundViewModel: BackgroundViewModel
+  private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
+  private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
+  private val defaultGatekeeperInfo = GatekeeperInfo.GatekeeperPasswordInfo(byteArrayOf(1, 3), 3)
+  private var testScope = TestScope(backgroundDispatcher)
+
+  private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
+
+  private fun initialize(gatekeeperInfo: GatekeeperInfo = defaultGatekeeperInfo) {
+    fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
+    gateKeeperViewModel =
+      FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
+          gatekeeperInfo,
+          fakeFingerprintManagerInteractor
+        )
+        .create(FingerprintGatekeeperViewModel::class.java)
+
+    navigationViewModel =
+      FingerprintEnrollNavigationViewModel(
+        backgroundDispatcher,
+        fakeFingerprintManagerInteractor,
+        gateKeeperViewModel,
+        Enrollment,
+        NavState(true),
+        Default,
+      )
+
+    backgroundViewModel =
+      BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
+    backgroundViewModel.inForeground()
+    val fingerprintEnrollViewModel =
+      FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
+          fakeFingerprintManagerInteractor,
+          gateKeeperViewModel,
+          navigationViewModel,
+        )
+        .create(FingerprintEnrollViewModel::class.java)
+    enrollEnrollingViewModel =
+      FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
+          fingerprintEnrollViewModel,
+          backgroundViewModel,
+        )
+        .create(FingerprintEnrollEnrollingViewModel::class.java)
+  }
+
+  @Before
+  fun setup() {
+    Dispatchers.setMain(backgroundDispatcher)
+    initialize()
+  }
+
+  @After
+  fun tearDown() {
+    Dispatchers.resetMain()
+  }
+
+  @Test
+  fun testEnrollShouldBeFalse() =
+    testScope.runTest {
+      var shouldEnroll = false
+
+      val job = launch {
+        enrollEnrollingViewModel.enrollFlowShouldBeRunning.collect { shouldEnroll = it }
+      }
+
+      assertThat(shouldEnroll).isFalse()
+      runCurrent()
+
+      enrollEnrollingViewModel.canEnroll()
+      runCurrent()
+
+      assertThat(shouldEnroll).isTrue()
+      job.cancel()
+    }
+
+  @Test
+  fun testEnrollShouldBeFalseWhenBackground() =
+    testScope.runTest {
+      var shouldEnroll = false
+
+      val job = launch {
+        enrollEnrollingViewModel.enrollFlowShouldBeRunning.collect { shouldEnroll = it }
+      }
+
+      assertThat(shouldEnroll).isFalse()
+      runCurrent()
+
+      enrollEnrollingViewModel.canEnroll()
+      runCurrent()
+
+      assertThat(shouldEnroll).isTrue()
+
+      backgroundViewModel.wentToBackground()
+      runCurrent()
+      assertThat(shouldEnroll).isFalse()
+
+      job.cancel()
+    }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
index d4dbec5..064e087 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import com.android.settings.biometrics.BiometricEnrollBase
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FinishSettings
@@ -208,7 +208,7 @@
   fun enrollAdditionalFingerprints_fails() =
     testScope.runTest {
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
+        mutableListOf(FingerprintData("a", 1, 3L))
       fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1))
 
       var nextStep: NextStepViewModel? = null
@@ -227,7 +227,7 @@
   fun enrollAdditional_success() =
     testScope.runTest {
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
+        mutableListOf(FingerprintData("a", 1, 3L))
 
       var nextStep: NextStepViewModel? = null
       val job = launch { underTest.nextStep.collect { nextStep = it } }
@@ -245,7 +245,7 @@
   fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() =
     testScope.runTest {
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
+        mutableListOf(FingerprintData("a", 1, 3L))
       fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3))
 
       var nextStep: NextStepViewModel? = null
@@ -320,7 +320,7 @@
   fun showSettings_shouldFinish() =
     testScope.runTest {
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
+        mutableListOf(FingerprintData("a", 1, 3L))
 
       var nextStep: NextStepViewModel? = null
       val job = launch { underTest.nextStep.collect { nextStep = it } }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
index d25ced0..4bd9121 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
@@ -17,8 +17,8 @@
 package com.android.settings.fingerprint2.ui.settings
 
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
+import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.PreferenceViewModel
@@ -103,7 +103,7 @@
           FingerprintSensorType.UDFPS_OPTICAL,
         )
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
+        mutableListOf(FingerprintData("a", 1, 3L))
 
       underTest =
         FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -114,7 +114,7 @@
           )
           .create(FingerprintSettingsViewModel::class.java)
 
-      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      var authAttempt: FingerprintAuthAttemptModel? = null
       val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
 
       underTest.shouldAuthenticate(true)
@@ -139,7 +139,7 @@
           FingerprintSensorType.UDFPS_ULTRASONIC,
         )
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
+        mutableListOf(FingerprintData("a", 1, 3L))
 
       underTest =
         FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -150,7 +150,7 @@
           )
           .create(FingerprintSettingsViewModel::class.java)
 
-      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      var authAttempt: FingerprintAuthAttemptModel? = null
       val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
 
       underTest.shouldAuthenticate(true)
@@ -173,8 +173,8 @@
           FingerprintSensorType.POWER_BUTTON
         )
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
-        mutableListOf(FingerprintViewModel("a", 1, 3L))
-      val success = FingerprintAuthAttemptViewModel.Success(1)
+        mutableListOf(FingerprintData("a", 1, 3L))
+      val success = FingerprintAuthAttemptModel.Success(1)
       fakeFingerprintManagerInteractor.authenticateAttempt = success
 
       underTest =
@@ -186,7 +186,7 @@
           )
           .create(FingerprintSettingsViewModel::class.java)
 
-      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      var authAttempt: FingerprintAuthAttemptModel? = null
 
       val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
       underTest.shouldAuthenticate(true)
@@ -200,7 +200,7 @@
 
   @Test
   fun deleteDialog_showAndDismiss() = runTest {
-    val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
+    val fingerprintToDelete = FingerprintData("A", 1, 10L)
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
       mutableListOf(fingerprintToDelete)
 
@@ -236,7 +236,7 @@
 
   @Test
   fun renameDialog_showAndDismiss() = runTest {
-    val fingerprintToRename = FingerprintViewModel("World", 1, 10L)
+    val fingerprintToRename = FingerprintData("World", 1, 10L)
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
       mutableListOf(fingerprintToRename)
 
@@ -274,7 +274,7 @@
 
   @Test
   fun testTwoDialogsCannotShow_atSameTime() = runTest {
-    val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
+    val fingerprintToDelete = FingerprintData("A", 1, 10L)
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
       mutableListOf(fingerprintToDelete)
 
@@ -311,9 +311,9 @@
   fun authenticatePauses_whenPaused() =
     testScope.runTest {
       val fingerprints = setupAuth()
-      val success = FingerprintAuthAttemptViewModel.Success(fingerprints.first().fingerId)
+      val success = FingerprintAuthAttemptModel.Success(fingerprints.first().fingerId)
 
-      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      var authAttempt: FingerprintAuthAttemptModel? = null
 
       val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
 
@@ -325,7 +325,7 @@
       assertThat(authAttempt).isEqualTo(success)
 
       fakeFingerprintManagerInteractor.authenticateAttempt =
-        FingerprintAuthAttemptViewModel.Success(10)
+        FingerprintAuthAttemptModel.Success(10)
       underTest.shouldAuthenticate(false)
       advanceTimeBy(400)
       runCurrent()
@@ -340,7 +340,7 @@
     testScope.runTest {
       val fingerprints = setupAuth()
 
-      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      var authAttempt: FingerprintAuthAttemptModel? = null
       val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
       underTest.shouldAuthenticate(true)
       navigationViewModel.onConfirmDevice(true, 10L)
@@ -357,7 +357,7 @@
     testScope.runTest {
       val fingerprints = setupAuth()
 
-      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      var authAttempt: FingerprintAuthAttemptModel? = null
       val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
       underTest.shouldAuthenticate(true)
       navigationViewModel.onConfirmDevice(true, 10L)
@@ -370,7 +370,7 @@
       assertThat(authAttempt).isEqualTo(null)
     }
 
-  private fun setupAuth(): MutableList<FingerprintViewModel> {
+  private fun setupAuth(): MutableList<FingerprintData> {
     fakeFingerprintManagerInteractor.sensorProp =
       FingerprintSensor(
         0 /* sensorId */,
@@ -379,9 +379,9 @@
         FingerprintSensorType.POWER_BUTTON
       )
     val fingerprints =
-      mutableListOf(FingerprintViewModel("a", 1, 3L), FingerprintViewModel("b", 2, 5L))
+      mutableListOf(FingerprintData("a", 1, 3L), FingerprintData("b", 2, 5L))
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fingerprints
-    val success = FingerprintAuthAttemptViewModel.Success(1)
+    val success = FingerprintAuthAttemptModel.Success(1)
     fakeFingerprintManagerInteractor.authenticateAttempt = success
 
     underTest =
diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
index 31b8e79..5ac367e 100644
--- a/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/localepicker/LocaleHelperPreferenceControllerTest.java
@@ -19,12 +19,14 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.verify;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Looper;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settingslib.widget.FooterPreference;
 
 import org.junit.Before;
@@ -37,6 +39,7 @@
 public class LocaleHelperPreferenceControllerTest {
     private Context mContext;
     private LocaleHelperPreferenceController mLocaleHelperPreferenceController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Mock
     private FooterPreference mMockFooterPreference;
@@ -49,11 +52,16 @@
         }
         mContext = ApplicationProvider.getApplicationContext();
         mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(mContext);
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
     }
 
     @Test
     public void updateFooterPreference_setFooterPreference_hasClickAction() {
         mLocaleHelperPreferenceController.updateFooterPreference(mMockFooterPreference);
         verify(mMockFooterPreference).setLearnMoreText(anyString());
+        mMockFooterPreference.setLearnMoreAction(v -> {
+            verify(mFeatureFactory.metricsFeatureProvider).action(
+                    mContext, SettingsEnums.ACTION_LANGUAGES_LEARN_MORE);
+        });
     }
 }
diff --git a/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java b/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java
index 0a67824..5c42ad9 100644
--- a/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java
+++ b/tests/unit/src/com/android/settings/regionalpreferences/NumberingSystemItemControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -51,6 +52,7 @@
     private NumberingPreferencesFragment mFragment;
     private PreferenceScreen mPreferenceScreen;
     private LocaleList mCacheLocale;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     @UiThreadTest
@@ -59,6 +61,7 @@
             Looper.prepare();
         }
         mApplicationContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mFragment = spy(new NumberingPreferencesFragment());
         PreferenceManager preferenceManager = new PreferenceManager(mApplicationContext);
         mPreferenceScreen = preferenceManager.createPreferenceScreen(mApplicationContext);
@@ -94,6 +97,10 @@
         }
 
         assertTrue(isCallingStartActivity);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                mApplicationContext,
+                SettingsEnums.ACTION_CHOOSE_LANGUAGE_FOR_NUMBERS_PREFERENCES,
+                "I_am_the_key");
     }
 
     @Test
@@ -114,6 +121,9 @@
         mController.handlePreferenceTreeClick(preference);
 
         verify(mFragment).setArguments(any());
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                mApplicationContext, SettingsEnums.ACTION_SET_NUMBERS_PREFERENCES,
+                "test_key");
     }
 
     @Test