Basic structure for fingerprint enrollment.

Bug: N/A
Test: Enroll introduction screen works as expected
Test: User is prompted with pin/pattern/pass if the token is not
present.

Change-Id: I32a182b09c3bcd9be43428c500bfae7b39a74e63
diff --git a/Android.bp b/Android.bp
index 861f95f..db52d18 100644
--- a/Android.bp
+++ b/Android.bp
@@ -106,7 +106,9 @@
         "SystemUIUnfoldLib",
     ],
 
-    plugins: ["androidx.room_room-compiler-plugin"],
+    plugins: [
+        "androidx.room_room-compiler-plugin",
+    ],
 
     libs: [
         "telephony-common",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2c61ec6..c6e1e5d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2566,6 +2566,16 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".biometrics.fingerprint2.enrollment.ui.activity.FingerprintEnrollmentV2Activity"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_FINGERPRINT"
+            android:theme="@style/GlifTheme.Light">
+            <intent-filter>
+                <action android:name="android.settings.FINGERPRINT_SETUP" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".biometrics.fingerprint.FingerprintSuggestionActivity"
             android:exported="true"
             android:permission="android.permission.MANAGE_FINGERPRINT"
diff --git a/res/layout/fingerprint_v2_enroll_find_sensor.xml b/res/layout/fingerprint_v2_enroll_find_sensor.xml
new file mode 100644
index 0000000..d2a495d
--- /dev/null
+++ b/res/layout/fingerprint_v2_enroll_find_sensor.xml
@@ -0,0 +1,44 @@
+<?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"
+android:id="@+id/setup_wizard_layout"
+style="?attr/fingerprint_layout_theme"
+android:layout_width="match_parent"
+android:layout_height="match_parent">
+
+<LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:clipToPadding="false"
+    android:clipChildren="false">
+
+    <Space
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
+
+    <include
+        layout="@layout/fingerprint_enroll_find_sensor_graphic"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"/>
+
+</LinearLayout>
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/res/layout/fingerprint_v2_enroll_introduction.xml b/res/layout/fingerprint_v2_enroll_introduction.xml
new file mode 100644
index 0000000..e9dd08a
--- /dev/null
+++ b/res/layout/fingerprint_v2_enroll_introduction.xml
@@ -0,0 +1,214 @@
+<?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
+    style="@style/SudContentFrame"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="vertical">
+
+    <com.google.android.setupdesign.view.RichTextView
+        android:id="@+id/error_text"
+        style="@style/SudDescription.Glif"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <ImageView
+            style="@style/SudContentIllustration"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:contentDescription="@null"
+            android:src="@drawable/fingerprint_enroll_introduction" />
+
+    </FrameLayout>
+
+    <!-- Contains the extra information text at the bottom -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <!-- How it works -->
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/BiometricEnrollIntroTitle"
+            android:text="@string/security_settings_fingerprint_v2_enroll_introduction_footer_title_2" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/icon_fingerprint"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_fingerprint_24dp"/>
+            <Space
+                android:layout_width="16dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/footer_message_2"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/BiometricEnrollIntroMessage" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/icon_device_locked"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_lock_24dp"/>
+            <Space
+                android:layout_width="16dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/footer_message_3"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/BiometricEnrollIntroMessage" />
+        </LinearLayout>
+
+        <!-- You're in control -->
+        <TextView
+            android:id="@+id/footer_title_1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/BiometricEnrollIntroTitle" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/icon_trash_can"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_trash_can"/>
+            <Space
+                android:layout_width="16dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/footer_message_4"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/BiometricEnrollIntroMessage" />
+        </LinearLayout>
+
+        <!-- Keep in mind -->
+        <TextView
+            android:id="@+id/footer_title_2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="@style/BiometricEnrollIntroTitle"
+            android:text="@string/security_settings_face_enroll_introduction_info_title"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/icon_info"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_info_outline_24dp"/>
+            <Space
+                android:layout_width="16dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/footer_message_5"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/BiometricEnrollIntroMessage" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/icon_shield"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_guarantee"/>
+            <Space
+                android:layout_width="16dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/footer_message_6"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/BiometricEnrollIntroMessage" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:id="@+id/icon_link"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:contentDescription="@null"
+                android:src="@drawable/ic_link_24dp"/>
+            <Space
+                android:layout_width="16dp"
+                android:layout_height="wrap_content"/>
+            <TextView
+                android:id="@+id/footer_learn_more"
+                android:linksClickable="true"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/BiometricEnrollIntroMessage"
+                android:paddingBottom="0dp"
+                android:text="@string/security_settings_fingerprint_v2_enroll_introduction_message_learn_more" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/res/layout/fingerprint_v2_enroll_main.xml b/res/layout/fingerprint_v2_enroll_main.xml
new file mode 100644
index 0000000..b3d6c3d
--- /dev/null
+++ b/res/layout/fingerprint_v2_enroll_main.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <androidx.fragment.app.FragmentContainerView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/fragment_container_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+    />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/activity/FingerprintEnrollmentV2Activity.kt
new file mode 100644
index 0000000..7bea4b4
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/activity/FingerprintEnrollmentV2Activity.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.enrollment.ui.activity
+
+import android.annotation.ColorInt
+import android.app.Activity
+import android.content.Intent
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.hardware.fingerprint.FingerprintManager
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+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.enrollment.ui.fragment.FingerprintEnrollConfirmationV2Fragment
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollEnrollingV2Fragment
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollFindSensorV2Fragment
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.fragment.FingerprintEnrollmentIntroV2Fragment
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Confirmation
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Education
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintScrollViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Finish
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.GatekeeperInfo
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Intro
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.LaunchConfirmDeviceCredential
+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.google.android.setupdesign.util.ThemeHelper
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintEnrollmentV2Activity"
+
+/**
+ * This is the activity that controls the entire Fingerprint Enrollment experience through its
+ * children fragments.
+ */
+class FingerprintEnrollmentV2Activity : FragmentActivity() {
+  private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
+  private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
+  private val coroutineDispatcher = Dispatchers.Default
+
+  /** Result listener for ChooseLock activity flow. */
+  private val confirmDeviceResultListener =
+    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+      val resultCode = result.resultCode
+      val data = result.data
+      onConfirmDevice(resultCode, data)
+    }
+
+  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+    super.onActivityResult(requestCode, resultCode, data)
+    if (requestCode == CONFIRM_REQUEST) {
+      onConfirmDevice(resultCode, data)
+    }
+  }
+
+  override fun onAttachedToWindow() {
+    window.statusBarColor = getBackgroundColor()
+    super.onAttachedToWindow()
+  }
+
+  @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?
+    lifecycleScope.launch {
+      gatekeeperViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
+    }
+  }
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    setContentView(R.layout.fingerprint_v2_enroll_main)
+
+    setTheme(SetupWizardUtils.getTheme(applicationContext, intent))
+    ThemeHelper.trySetDynamicColor(applicationContext)
+
+    val backgroundDispatcher = Dispatchers.IO
+
+    val context = applicationContext
+    val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
+
+    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
+      }
+
+    var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
+    val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
+    val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)
+
+    gatekeeperViewModel =
+      ViewModelProvider(
+        this,
+        FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
+          gatekeeperInfo,
+          interactor,
+        )
+      )[FingerprintGatekeeperViewModel::class.java]
+
+    navigationViewModel =
+      ViewModelProvider(
+        this,
+        FingerprintEnrollmentNavigationViewModel.FingerprintEnrollmentNavigationViewModelFactory(
+          backgroundDispatcher,
+          interactor,
+          gatekeeperViewModel,
+          gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
+        )
+      )[FingerprintEnrollmentNavigationViewModel::class.java]
+
+    // Initialize FingerprintViewModel
+    ViewModelProvider(this, FingerprintViewModel.FingerprintViewModelFactory(interactor))[
+      FingerprintViewModel::class.java]
+
+    // Initialize scroll view model
+    ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
+      FingerprintScrollViewModel::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 -> FingerprintEnrollmentIntroV2Fragment::class.java as Class<Fragment>
+            else -> null
+          }
+
+        if (theClass != null) {
+          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 =
+      intent.getBooleanExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, false)
+    if (
+      fromSettingsSummary && GatekeeperPasswordProvider.containsGatekeeperPasswordHandle(intent)
+    ) {
+      overridePendingTransition(
+        com.google.android.setupdesign.R.anim.sud_slide_next_in,
+        com.google.android.setupdesign.R.anim.sud_slide_next_out
+      )
+    }
+  }
+
+  private fun launchConfirmOrChooseLock(userId: Int) {
+    val activity = this
+    lifecycleScope.launch(coroutineDispatcher) {
+      val intent = Intent()
+      val builder = ChooseLockSettingsHelper.Builder(activity)
+      val launched =
+        builder
+          .setRequestCode(CONFIRM_REQUEST)
+          .setTitle(getString(R.string.security_settings_fingerprint_preference_title))
+          .setRequestGatekeeperPasswordHandle(true)
+          .setUserId(userId)
+          .setForegroundOnly(true)
+          .setReturnCredentials(true)
+          .show()
+      if (!launched) {
+        intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric::class.java.name)
+        intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true)
+        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
+        intent.putExtra(Intent.EXTRA_USER_ID, userId)
+        confirmDeviceResultListener.launch(intent)
+      }
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollConfirmationV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollConfirmationV2Fragment.kt
new file mode 100644
index 0000000..84a5658
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollConfirmationV2Fragment.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.enrollment.ui.fragment
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
+
+/**
+ * A fragment to indicate that fingerprint enrollment has been completed.
+ *
+ * This page will display basic information about what a fingerprint can be used for and acts as the
+ * final step of enrollment.
+ */
+class FingerprintEnrollConfirmationV2Fragment : Fragment() {
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    if (savedInstanceState == null) {
+      val navigationViewModel =
+        ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollEnrollingV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollEnrollingV2Fragment.kt
new file mode 100644
index 0000000..846bad7
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollEnrollingV2Fragment.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.enrollment.ui.fragment
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
+
+/** A fragment that is responsible for enrolling a users fingerprint. */
+class FingerprintEnrollEnrollingV2Fragment : Fragment() {
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    if (savedInstanceState == null) {
+      val navigationViewModel =
+        ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollFindSensorV2Fragment.kt
new file mode 100644
index 0000000..6b07467
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollFindSensorV2Fragment.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.enrollment.ui.fragment
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
+
+/**
+ * A fragment that is used to educate the user about the fingerprint sensor on this device.
+ *
+ * The main goals of this page are
+ * 1. Inform the user where the fingerprint sensor is on their device
+ * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
+ *    will work.
+ */
+class FingerprintEnrollFindSensorV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_find_sensor) {
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    if (savedInstanceState == null) {
+      val navigationViewModel =
+        ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollmentIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollmentIntroV2Fragment.kt
new file mode 100644
index 0000000..14229ee
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/fragment/FingerprintEnrollmentIntroV2Fragment.kt
@@ -0,0 +1,290 @@
+/*
+ * 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.enrollment.ui.fragment
+
+import android.annotation.NonNull
+import android.annotation.StringRes
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.os.Bundle
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import android.widget.ScrollView
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintEnrollmentNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintGatekeeperViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintScrollViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.enrollment.ui.viewmodel.Unicorn
+import com.google.android.setupcompat.template.FooterBarMixin
+import com.google.android.setupcompat.template.FooterButton
+import com.google.android.setupdesign.GlifLayout
+import com.google.android.setupdesign.template.RequireScrollMixin
+import com.google.android.setupdesign.util.DynamicColorPalette
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintEnrollmentIntroV2Fragment"
+
+/** This class represents the customizable text for FingerprintEnrollIntroduction. */
+private data class TextModel(
+  @StringRes val footerMessageTwo: Int,
+  @StringRes val footerMessageThree: Int,
+  @StringRes val footerMessageFour: Int,
+  @StringRes val footerMessageFive: Int,
+  @StringRes val footerMessageSix: Int,
+  @StringRes val negativeButton: Int,
+  @StringRes val footerTitleOne: Int,
+  @StringRes val footerTitleTwo: Int,
+  @StringRes val headerText: Int,
+  @StringRes val descriptionText: Int,
+)
+
+/**
+ * The introduction fragment that is used to inform the user the basics of what a fingerprint sensor
+ * is and how it will be used.
+ *
+ * The main gaols of this page are
+ * 1. Inform the user what the fingerprint sensor is and does
+ * 2. How the data will be stored
+ * 3. How the user can access and remove their data
+ */
+class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
+  private lateinit var footerBarMixin: FooterBarMixin
+  private lateinit var textModel: TextModel
+  private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
+  private lateinit var fingerprintStateViewModel: FingerprintViewModel
+  private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
+  private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    navigationViewModel =
+      ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
+    fingerprintStateViewModel =
+      ViewModelProvider(requireActivity())[FingerprintViewModel::class.java]
+    fingerprintScrollViewModel =
+      ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
+    gateKeeperViewModel =
+      ViewModelProvider(requireActivity())[FingerprintGatekeeperViewModel::class.java]
+  }
+
+  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+    super.onViewCreated(view, savedInstanceState)
+
+    lifecycleScope.launch {
+      combine(
+          navigationViewModel.enrollType,
+          fingerprintStateViewModel.fingerprintStateViewModel,
+        ) { enrollType, fingerprintStateViewModel ->
+          Pair(enrollType, fingerprintStateViewModel)
+        }
+        .collect { (enrollType, fingerprintStateViewModel) ->
+          val sensorProps = fingerprintStateViewModel?.sensorProps
+
+          textModel =
+            when (enrollType) {
+              Unicorn -> getUnicornTextModel()
+              else -> getNormalTextModel()
+            }
+
+          setupFooterBarAndScrollView(view)
+
+          if (savedInstanceState == null) {
+            getLayout()?.setHeaderText(textModel.headerText)
+            getLayout()?.setDescriptionText(textModel.descriptionText)
+
+            // Set color filter for the following icons.
+            val colorFilter = getIconColorFilter()
+            listOf(
+                R.id.icon_fingerprint,
+                R.id.icon_device_locked,
+                R.id.icon_trash_can,
+                R.id.icon_info,
+                R.id.icon_shield,
+                R.id.icon_link
+              )
+              .forEach { icon ->
+                view.findViewById<ImageView>(icon).drawable.colorFilter = colorFilter
+              }
+
+            // Set the text for the footer text views.
+            listOf(
+                R.id.footer_message_2 to textModel.footerMessageTwo,
+                R.id.footer_message_3 to textModel.footerMessageThree,
+                R.id.footer_message_4 to textModel.footerMessageFour,
+                R.id.footer_message_5 to textModel.footerMessageFive,
+                R.id.footer_message_6 to textModel.footerMessageSix,
+              )
+              .forEach { pair -> view.findViewById<TextView>(pair.first).setText(pair.second) }
+
+            setFooterLink(view)
+
+            val iconShield: ImageView = view.findViewById(R.id.icon_shield)
+            val footerMessage6: TextView = view.findViewById(R.id.footer_message_6)
+            when (sensorProps?.sensorType) {
+              FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+              FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> {
+                footerMessage6.visibility = View.VISIBLE
+                iconShield.visibility = View.VISIBLE
+              }
+              else -> {
+                footerMessage6.visibility = View.GONE
+                iconShield.visibility = View.GONE
+              }
+            }
+
+            view.findViewById<TextView?>(R.id.footer_title_1).setText(textModel.footerTitleOne)
+            view.findViewById<TextView?>(R.id.footer_title_2).setText(textModel.footerTitleOne)
+          }
+        }
+    }
+  }
+
+  private fun setFooterLink(view: View) {
+    val footerLink: TextView = view.findViewById(R.id.footer_learn_more)
+    footerLink.movementMethod = LinkMovementMethod.getInstance()
+    footerLink.text =
+      Html.fromHtml(
+        getString(R.string.security_settings_fingerprint_v2_enroll_introduction_message_learn_more),
+        Html.FROM_HTML_MODE_LEGACY
+      )
+  }
+
+  private fun setupFooterBarAndScrollView(
+    view: View,
+  ) {
+    val scrollView: ScrollView =
+      view.findViewById(com.google.android.setupdesign.R.id.sud_scroll_view)
+    scrollView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+    // Next button responsible for starting the next fragment.
+    val onNextButtonClick: View.OnClickListener =
+      View.OnClickListener { Log.d(TAG, "OnNextClicked") }
+
+    val layout: GlifLayout = requireActivity().findViewById(R.id.setup_wizard_layout)
+    footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
+    footerBarMixin.primaryButton =
+      FooterButton.Builder(requireActivity())
+        .setText(R.string.security_settings_face_enroll_introduction_more)
+        .setListener(onNextButtonClick)
+        .setButtonType(FooterButton.ButtonType.OPT_IN)
+        .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+        .build()
+    footerBarMixin.setSecondaryButton(
+      FooterButton.Builder(requireActivity())
+        .setText(textModel.negativeButton)
+        .setListener({ Log.d(TAG, "prevClicked") })
+        .setButtonType(FooterButton.ButtonType.NEXT)
+        .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+        .build(),
+      true /* usePrimaryStyle */
+    )
+
+    val primaryButton = footerBarMixin.primaryButton
+    val secondaryButton = footerBarMixin.secondaryButton
+
+    secondaryButton.visibility = View.INVISIBLE
+
+    val requireScrollMixin = layout.getMixin(RequireScrollMixin::class.java)
+    requireScrollMixin.requireScrollWithButton(
+      requireActivity(),
+      footerBarMixin.primaryButton,
+      R.string.security_settings_face_enroll_introduction_more,
+      onNextButtonClick
+    )
+
+    requireScrollMixin.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
+      // Show secondary button once scroll is completed.
+      if (!scrollNeeded) {
+        fingerprintScrollViewModel.userConsented()
+      }
+    }
+
+    lifecycleScope.launch {
+      fingerprintScrollViewModel.hasReadConsentScreen.collect { consented ->
+        if (consented) {
+          primaryButton.setText(
+            requireContext(),
+            R.string.security_settings_fingerprint_enroll_introduction_agree
+          )
+          secondaryButton.visibility = View.VISIBLE
+        } else {
+          secondaryButton.visibility = View.INVISIBLE
+        }
+      }
+    }
+
+    footerBarMixin.getButtonContainer()?.setBackgroundColor(Color.TRANSPARENT)
+
+    // I think I should remove this, and make the challenge a pre-requisite of launching
+    // the flow. For instance if someone launches the activity with an invalid challenge, it
+    // either 1) Fails or 2) Launched confirmDeviceCredential
+    primaryButton.isEnabled = false
+    lifecycleScope.launch {
+      gateKeeperViewModel.hasValidGatekeeperInfo.collect { primaryButton.isEnabled = it }
+    }
+  }
+
+  private fun getNormalTextModel() =
+    TextModel(
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_6,
+      R.string.security_settings_fingerprint_enroll_introduction_no_thanks,
+      R.string.security_settings_fingerprint_enroll_introduction_footer_title_1,
+      R.string.security_settings_fingerprint_enroll_introduction_footer_title_2,
+      R.string.security_settings_fingerprint_enroll_introduction_title,
+      R.string.security_settings_fingerprint_enroll_introduction_v3_message,
+    )
+
+  private fun getUnicornTextModel() =
+    TextModel(
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_2,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_3,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_4,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5,
+      R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6,
+      R.string.security_settings_fingerprint_enroll_introduction_no_thanks,
+      R.string.security_settings_fingerprint_enroll_introduction_footer_title_consent_1,
+      R.string.security_settings_fingerprint_enroll_introduction_footer_title_2,
+      R.string.security_settings_fingerprint_enroll_consent_introduction_title,
+      R.string.security_settings_fingerprint_enroll_introduction_v3_message,
+    )
+
+  @NonNull
+  private fun getIconColorFilter(): PorterDuffColorFilter {
+    return PorterDuffColorFilter(
+      DynamicColorPalette.getColor(context, DynamicColorPalette.ColorType.ACCENT),
+      PorterDuff.Mode.SRC_IN
+    )
+  }
+
+  private fun getLayout(): GlifLayout? {
+    return requireView().findViewById(R.id.setup_wizard_layout) as GlifLayout?
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintEnrolllmentNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintEnrolllmentNavigationViewModel.kt
new file mode 100644
index 0000000..d074fdd
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintEnrolllmentNavigationViewModel.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.enrollment.ui.viewmodel
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+const val TAG = "FingerprintEnrollmentNavigationViewModel"
+
+/** Interface to validate a gatekeeper hat */
+interface Validator {
+  fun validateGateKeeper(challenge: Long?): Boolean
+}
+
+/**
+ * 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
+ */
+class FingerprintEnrollmentNavigationViewModel(
+  private val dispatcher: CoroutineDispatcher,
+  private val validator: Validator,
+  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+  private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+  private val canSkipConfirm: Boolean
+) : ViewModel() {
+
+  private class InternalNavigationStep(
+    lastStep: NextStepViewModel,
+    nextStep: NextStepViewModel,
+    forward: Boolean,
+    var canNavigate: Boolean
+  ) : NavigationStep(lastStep, nextStep, forward)
+
+  private var _enrollType = MutableStateFlow<EnrollType?>(Default)
+
+  /** A flow that indicates the [EnrollType] */
+  val enrollType: Flow<EnrollType?> = _enrollType.asStateFlow()
+
+  private var navState = NavState(canSkipConfirm)
+
+  private val _navigationStep =
+    MutableStateFlow(
+      InternalNavigationStep(
+        PlaceHolderState,
+        Start.next(navState),
+        forward = false,
+        canNavigate = true
+      )
+    )
+
+  init {
+    viewModelScope.launch {
+      gatekeeperViewModel.credentialConfirmed.filterNotNull().collect {
+        if (_navigationStep.value.currStep is LaunchConfirmDeviceCredential) {
+          if (it) nextStep() else finish()
+        }
+      }
+    }
+  }
+
+  /**
+   * A flow that contains the [NavigationStep] used to indicate where in the enrollment process the
+   * user is.
+   */
+  val navigationViewModel: Flow<NavigationStep> = _navigationStep.asStateFlow()
+
+  /** Used to start the next step of Fingerprint Enrollment. */
+  fun nextStep() {
+    viewModelScope.launch {
+      val currStep = _navigationStep.value.currStep
+      val nextStep = currStep.next(navState)
+      Log.d(TAG, "nextStep(${currStep} -> $nextStep)")
+      _navigationStep.update {
+        InternalNavigationStep(currStep, nextStep, forward = true, canNavigate = false)
+      }
+    }
+  }
+
+  /** Go back a step of fingerprint enrollment. */
+  fun prevStep() {
+    viewModelScope.launch {
+      val currStep = _navigationStep.value.currStep
+      val nextStep = currStep.prev(navState)
+      _navigationStep.update {
+        InternalNavigationStep(currStep, nextStep, forward = false, canNavigate = false)
+      }
+    }
+  }
+
+  private fun finish() {
+    _navigationStep.update {
+      InternalNavigationStep(Finish(null), Finish(null), forward = false, canNavigate = false)
+    }
+  }
+
+  class FingerprintEnrollmentNavigationViewModelFactory(
+    private val backgroundDispatcher: CoroutineDispatcher,
+    private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+    private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
+    private val canSkipConfirm: Boolean,
+  ) : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+
+      return FingerprintEnrollmentNavigationViewModel(
+        backgroundDispatcher,
+        object : Validator {
+          override fun validateGateKeeper(challenge: Long?): Boolean {
+            return challenge != null
+          }
+        },
+        fingerprintManagerInteractor,
+        fingerprintGatekeeperViewModel,
+        canSkipConfirm,
+      )
+        as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintGatekeeperViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintGatekeeperViewModel.kt
new file mode 100644
index 0000000..8079f7a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintGatekeeperViewModel.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.enrollment.ui.viewmodel
+
+import android.os.CountDownTimer
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+sealed interface GatekeeperInfo {
+  object Invalid : GatekeeperInfo
+  object Timeout : GatekeeperInfo
+  data class GatekeeperPasswordInfo(val token: ByteArray?, val passwordHandle: Long?) :
+    GatekeeperInfo
+}
+
+/**
+ * This class is responsible for maintaining the gatekeeper information including things like
+ * timeouts.
+ *
+ * Please note, that this class can't fully support timeouts of the gatekeeper password handle due
+ * to the fact that a handle may have been generated earlier in the settings enrollment and passed
+ * in as a parameter to this class.
+ */
+class FingerprintGatekeeperViewModel(
+  theGatekeeperInfo: GatekeeperInfo?,
+  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+) : ViewModel() {
+
+  private var _gatekeeperInfo: MutableStateFlow<GatekeeperInfo?> =
+    MutableStateFlow(theGatekeeperInfo)
+
+  /** The gatekeeper info for fingerprint enrollment. */
+  val gatekeeperInfo: Flow<GatekeeperInfo?> = _gatekeeperInfo.asStateFlow()
+
+  /** Indicates if the gatekeeper info is valid. */
+  val hasValidGatekeeperInfo: Flow<Boolean> =
+    gatekeeperInfo.map { it is GatekeeperInfo.GatekeeperPasswordInfo }
+
+  private var _credentialConfirmed: MutableStateFlow<Boolean?> = MutableStateFlow(null)
+  val credentialConfirmed: Flow<Boolean?> = _credentialConfirmed.asStateFlow()
+
+  private var countDownTimer: CountDownTimer? = null
+
+  /** Timeout of 15 minutes for a generated challenge */
+  private val TIMEOUT: Long = 15 * 60 * 1000
+
+  /** Called after a confirm device credential attempt has been made. */
+  fun onConfirmDevice(wasSuccessful: Boolean, theGatekeeperPasswordHandle: Long?) {
+    if (!wasSuccessful) {
+      Log.d(TAG, "confirmDevice failed")
+      _gatekeeperInfo.update { GatekeeperInfo.Invalid }
+      _credentialConfirmed.update { false }
+    } else {
+      viewModelScope.launch {
+        val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
+        _gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) }
+        _credentialConfirmed.update { true }
+        startTimeout()
+      }
+    }
+  }
+
+  private fun startTimeout() {
+    countDownTimer?.cancel()
+    countDownTimer =
+      object : CountDownTimer(TIMEOUT, 1000) {
+        override fun onFinish() {
+          _gatekeeperInfo.update { GatekeeperInfo.Timeout }
+        }
+
+        override fun onTick(millisUntilFinished: Long) {}
+      }
+  }
+
+  companion object {
+    /**
+     * A function that checks if the challenge and token are valid, in which case a
+     * [GatekeeperInfo.GatekeeperPasswordInfo] is provided, else [GatekeeperInfo.Invalid]
+     */
+    fun toGateKeeperInfo(challenge: Long?, token: ByteArray?): GatekeeperInfo {
+      Log.d(TAG, "toGateKeeperInfo(${challenge == null}, ${token == null})")
+      if (challenge == null || token == null) {
+        return GatekeeperInfo.Invalid
+      }
+      return GatekeeperInfo.GatekeeperPasswordInfo(token, challenge)
+    }
+  }
+
+  class FingerprintGatekeeperViewModelFactory(
+    private val gatekeeperInfo: GatekeeperInfo?,
+    private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+  ) : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+      return FingerprintGatekeeperViewModel(gatekeeperInfo, fingerprintManagerInteractor) as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintScrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintScrollViewModel.kt
new file mode 100644
index 0000000..ad90fc7
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintScrollViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.enrollment.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** This class is responsible for ensuring a users consent to use FingerprintEnrollment. */
+class FingerprintScrollViewModel : ViewModel() {
+
+  private val _hasReadConsentScreen: MutableStateFlow<Boolean> = MutableStateFlow(false)
+  /** Indicates if a user has consented to FingerprintEnrollment */
+  val hasReadConsentScreen: Flow<Boolean> = _hasReadConsentScreen.asStateFlow()
+
+  /** Indicates that a user has consented to FingerprintEnrollment */
+  fun userConsented() {
+    _hasReadConsentScreen.update { true }
+  }
+
+  class FingerprintScrollViewModelFactory() : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+      return FingerprintScrollViewModel() as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintStateViewModel.kt
new file mode 100644
index 0000000..1acd15a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/FingerprintStateViewModel.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.enrollment.ui.viewmodel
+
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.last
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+/** Represents the fingerprint data nad the relevant state. */
+data class FingerprintStateViewModel(
+  val fingerprintViewModels: List<FingerEnrollmentViewModel>,
+  val canEnroll: Boolean,
+  val maxFingerprints: Int,
+  val sensorProps: FingerprintSensorPropertiesInternal,
+)
+
+/** Represents a fingerprint enrollment. */
+data class FingerEnrollmentViewModel(
+  val name: String,
+  val fingerId: Int,
+  val deviceId: Long,
+)
+
+/** Represents all of the fingerprint information needed for fingerprint enrollment. */
+class FingerprintViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
+  ViewModel() {
+
+  private val _fingerprintViewModel: MutableStateFlow<FingerprintStateViewModel?> =
+    MutableStateFlow(null)
+
+  /**
+   * A flow that contains a [FingerprintStateViewModel] which contains the relevant information for
+   * enrollment
+   */
+  val fingerprintStateViewModel: Flow<FingerprintStateViewModel?> =
+    _fingerprintViewModel.asStateFlow()
+
+  init {
+    viewModelScope.launch {
+      val enrolledFingerprints =
+        fingerprintManagerInteractor.enrolledFingerprints.last().map {
+          FingerEnrollmentViewModel(it.name, it.fingerId, it.deviceId)
+        }
+      val sensorProps = fingerprintManagerInteractor.sensorPropertiesInternal().first()
+      val maxFingerprints = 5
+      _fingerprintViewModel.update {
+        FingerprintStateViewModel(
+          enrolledFingerprints,
+          enrolledFingerprints.size < maxFingerprints,
+          maxFingerprints,
+          sensorProps,
+        )
+      }
+    }
+  }
+
+  class FingerprintViewModelFactory(val interactor: FingerprintManagerInteractor) :
+    ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+
+      return FingerprintViewModel(interactor) as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/NextStepViewModel.kt
new file mode 100644
index 0000000..a8a8077
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/enrollment/ui/viewmodel/NextStepViewModel.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.enrollment.ui.viewmodel
+
+/**
+ * A class that represents an action that the consumer should transition between lastStep and
+ * currStep and in what direction this transition is occurring (e.g. forward or backwards)
+ */
+open class NavigationStep(
+  val lastStep: NextStepViewModel,
+  val currStep: NextStepViewModel,
+  val forward: Boolean
+) {
+  override fun toString(): String {
+    return "lastStep=$lastStep, currStep=$currStep, forward=$forward"
+  }
+}
+
+/** The navigation state used by a [NavStep] to determine what the [NextStepViewModel] should be. */
+class NavState(val confirmedDevice: Boolean)
+
+interface NavStep<T> {
+  fun next(state: NavState): T
+  fun prev(state: NavState): T
+}
+
+/**
+ * A class to represent a high level step (I.E. EnrollmentIntroduction) for FingerprintEnrollment.
+ */
+sealed class NextStepViewModel : NavStep<NextStepViewModel>
+
+/**
+ * This is the initial state for the previous step, used to indicate that there have been no
+ * previous states.
+ */
+object PlaceHolderState : NextStepViewModel() {
+  override fun next(state: NavState): NextStepViewModel = Finish(null)
+
+  override fun prev(state: NavState): NextStepViewModel = Finish(null)
+}
+
+/**
+ * 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() {
+  override fun next(state: NavState): NextStepViewModel =
+    if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
+
+  override fun prev(state: NavState): NextStepViewModel = Finish(null)
+}
+
+/** State indicating enrollment has been completed */
+class Finish(val resultCode: Int?) : NextStepViewModel() {
+  override fun next(state: NavState): NextStepViewModel = Finish(resultCode)
+  override fun prev(state: NavState): NextStepViewModel = Finish(null)
+}
+
+/** State for the FingerprintEnrollment introduction */
+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() {
+  override fun next(state: NavState): NextStepViewModel = Enrollment
+  override fun prev(state: NavState): NextStepViewModel = Intro
+}
+
+/** State for the FingerprintEnrollment enrollment */
+object Enrollment : NextStepViewModel() {
+  override fun next(state: NavState): NextStepViewModel = Confirmation
+  override fun prev(state: NavState): NextStepViewModel = Education
+}
+
+/** State for the FingerprintEnrollment confirmation */
+object Confirmation : NextStepViewModel() {
+  override fun next(state: NavState): NextStepViewModel = Finish(0)
+  override fun prev(state: NavState): NextStepViewModel = Intro
+}
+
+/**
+ * State used to send the user to the ConfirmDeviceCredential activity. This activity can either
+ * confirm a users device credential, or have them create one.
+ */
+object LaunchConfirmDeviceCredential : NextStepViewModel() {
+  override fun next(state: NavState): NextStepViewModel = Intro
+  override fun prev(state: NavState): NextStepViewModel = Finish(0)
+}