Moving FingerprintSettings to Kotlin
This change is the first of many, it will
1. Change java -> kotlin
2. Use the MVVM architecture
3. Be feature flagged
This change in particular is focused on transitions to and from various activities.
Enable feature via
adb shell setprop sys.fflag.override.settings_biometrics2_fingerprint true
Bug: 280862076
Test: atest FingerprintSettingsViewModelTest
Change-Id: I8eb5c30e6f2e92c256ae7c257a9d560439ba418f
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index a8fa527..3efa18f 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -73,6 +73,7 @@
}
public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class FingerprintSettingsActivityV2 extends SettingsActivity { /* empty */ }
public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
public static class TetherSettingsActivity extends SettingsActivity {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/binder/FingerprintViewBinder.kt b/src/com/android/settings/biometrics/fingerprint2/ui/binder/FingerprintViewBinder.kt
new file mode 100644
index 0000000..d4249ff
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/binder/FingerprintViewBinder.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.binder
+
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollAdditionalFingerprint
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a [FingerprintSettingsViewModel] to a [FingerprintSettingsV2Fragment]
+ */
+object FingerprintViewBinder {
+
+ interface Binding {
+ fun onConfirmDevice(wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?)
+ fun onEnrollSuccess()
+ fun onEnrollAdditionalFailure()
+ fun onEnrollFirstFailure(reason: String)
+ fun onEnrollFirstFailure(reason: String, resultCode: Int)
+ fun onEnrollFirst(token: ByteArray?, keyChallenge: Long?)
+ }
+
+ /** Initial listener for the first enrollment request */
+ fun bind(
+ viewModel: FingerprintSettingsViewModel,
+ lifecycleScope: LifecycleCoroutineScope,
+ token: ByteArray?,
+ challenge: Long?,
+ launchFullFingerprintEnrollment: (
+ userId: Int,
+ gateKeeperPasswordHandle: Long?,
+ challenge: Long?,
+ challengeToken: ByteArray?
+ ) -> Unit,
+ launchAddFingerprint: (userId: Int, challengeToken: ByteArray?) -> Unit,
+ launchConfirmOrChooseLock: (userId: Int) -> Unit,
+ finish: () -> Unit,
+ setResultExternal: (resultCode: Int) -> Unit,
+ ): Binding {
+
+ lifecycleScope.launch {
+ viewModel.nextStep.filterNotNull().collect { nextStep ->
+ when (nextStep) {
+ is EnrollFirstFingerprint -> launchFullFingerprintEnrollment(
+ nextStep.userId,
+ nextStep.gateKeeperPasswordHandle,
+ nextStep.challenge,
+ nextStep.challengeToken
+ )
+
+ is EnrollAdditionalFingerprint -> launchAddFingerprint(
+ nextStep.userId, nextStep.challengeToken
+ )
+
+ is LaunchConfirmDeviceCredential -> launchConfirmOrChooseLock(nextStep.userId)
+
+ is FinishSettings -> {
+ println("Finishing due to ${nextStep.reason}")
+ finish()
+ }
+
+ is FinishSettingsWithResult -> {
+ println("Finishing with result ${nextStep.result} due to ${nextStep.reason}")
+ setResultExternal(nextStep.result)
+ finish()
+ }
+
+ is ShowSettings -> println("show settings")
+ }
+
+ viewModel.onUiCommandExecuted()
+ }
+ }
+
+ viewModel.updateTokenAndChallenge(token, if (challenge == -1L) null else challenge)
+
+ return object : Binding {
+ override fun onConfirmDevice(
+ wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?
+ ) {
+ viewModel.onConfirmDevice(wasSuccessful, theGateKeeperPasswordHandle)
+ }
+
+ override fun onEnrollSuccess() {
+ viewModel.onEnrollSuccess()
+ }
+
+ override fun onEnrollAdditionalFailure() {
+ viewModel.onEnrollAdditionalFailure()
+ }
+
+ override fun onEnrollFirstFailure(reason: String) {
+ viewModel.onEnrollFirstFailure(reason)
+ }
+
+ override fun onEnrollFirstFailure(reason: String, resultCode: Int) {
+ viewModel.onEnrollFirstFailure(reason, resultCode)
+ }
+
+ override fun onEnrollFirst(token: ByteArray?, keyChallenge: Long?) {
+ viewModel.onEnrollFirst(token, keyChallenge)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsV2Fragment.kt
new file mode 100644
index 0000000..9b85564
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsV2Fragment.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.fragment
+
+import android.app.Activity
+import android.app.settings.SettingsEnums
+import android.content.Context.FINGERPRINT_SERVICE
+import android.content.Intent
+import android.hardware.fingerprint.FingerprintManager
+import android.os.Bundle
+import android.util.FeatureFlagUtils
+import android.util.Log
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.biometrics.BiometricEnrollBase
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
+import com.android.settings.biometrics.fingerprint2.ui.binder.FingerprintViewBinder
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
+import com.android.settings.core.SettingsBaseActivity
+import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.password.ChooseLockGeneric
+import com.android.settings.password.ChooseLockSettingsHelper
+import com.android.settingslib.transition.SettingsTransitionHelper
+
+const val TAG = "FingerprintSettingsV2Fragment"
+
+/**
+ * A class responsible for showing FingerprintSettings. Typical activity Flows are
+ * 1. Settings > FingerprintSettings > PIN/PATTERN/PASS -> FingerprintSettings
+ * 2. FingerprintSettings -> FingerprintEnrollment fow
+ *
+ * This page typically allows for
+ * 1. Fingerprint deletion
+ * 2. Fingerprint enrollment
+ * 3. Renaming a fingerprint
+ * 4. Enabling/Disabling a feature
+ */
+class FingerprintSettingsV2Fragment : DashboardFragment() {
+ private lateinit var binding: FingerprintViewBinder.Binding
+
+ private val launchFirstEnrollmentListener =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+
+ val resultCode = result.resultCode
+ val data = result.data
+
+ Log.d(
+ TAG, "onEnrollFirstFingerprint($resultCode, $data)"
+ )
+ if (resultCode != BiometricEnrollBase.RESULT_FINISHED || data == null) {
+ if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
+ binding.onEnrollFirstFailure(
+ "Received RESULT_TIMEOUT when enrolling", resultCode
+ )
+ } else {
+ binding.onEnrollFirstFailure("Incorrect resultCode or data was null")
+ }
+ } else {
+ val token =
+ data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
+ val keyChallenge = data.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
+ binding.onEnrollFirst(token, keyChallenge)
+ }
+ }
+
+ /** Result listener for launching enrollments **after** a user has reached the settings page. */
+ private val launchAdditionalFingerprintListener =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ val resultCode = result.resultCode
+ Log.d(
+ TAG, "onEnrollAdditionalFingerprint($resultCode)"
+ )
+
+ if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
+ binding.onEnrollAdditionalFailure()
+ } else {
+ binding.onEnrollSuccess()
+ }
+ }
+
+ /** 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?) {
+ // This is needed to support ChooseLockSettingBuilder...show(). All other activity
+ // calls should use the registerForActivity method call.
+ super.onActivityResult(requestCode, resultCode, data)
+ val wasSuccessful =
+ resultCode == BiometricEnrollBase.RESULT_FINISHED || resultCode == Activity.RESULT_OK
+ val gateKeeperPasswordHandle =
+ data?.getExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE) as Long?
+ binding.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
+ }
+
+
+ override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ if (!FeatureFlagUtils.isEnabled(
+ context, FeatureFlagUtils.SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS
+ )
+ ) {
+ Log.d(
+ TAG, "Finishing due to feature not being enabled"
+ )
+ finish()
+ return
+ }
+ val viewModel = ViewModelProvider(
+ this, FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+ requireContext().applicationContext.userId, requireContext().getSystemService(
+ FINGERPRINT_SERVICE
+ ) as FingerprintManager
+ )
+ )[FingerprintSettingsViewModel::class.java]
+
+ val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
+ val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
+
+ binding = FingerprintViewBinder.bind(
+ viewModel,
+ lifecycleScope,
+ token,
+ challenge,
+ ::launchFullFingerprintEnrollment,
+ ::launchAddFingerprint,
+ ::launchConfirmOrChooseLock,
+ ::finish,
+ ::setResultExternal,
+ )
+ }
+
+ override fun getMetricsCategory(): Int {
+ return SettingsEnums.FINGERPRINT
+ }
+
+ override fun getPreferenceScreenResId(): Int {
+ return R.xml.security_settings_fingerprint_limbo
+ }
+
+ override fun getLogTag(): String {
+ return TAG
+ }
+
+ /**
+ * Helper function that will try and launch confirm lock, if that fails we will prompt user
+ * to choose a PIN/PATTERN/PASS.
+ */
+ private fun launchConfirmOrChooseLock(userId: Int) {
+ val intent = Intent()
+ val builder = ChooseLockSettingsHelper.Builder(requireActivity(), this)
+ val launched = builder.setRequestCode(BiometricEnrollBase.CONFIRM_REQUEST)
+ .setTitle(getString(R.string.security_settings_fingerprint_preference_title))
+ .setRequestGatekeeperPasswordHandle(true).setUserId(userId).setForegroundOnly(true)
+ .setReturnCredentials(true).show()
+ if (!launched) {
+ intent.setClassName(
+ Utils.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)
+ }
+ }
+
+ /**
+ * Helper for confirming a PIN/PATTERN/PASS
+ */
+ private fun onConfirmDevice(resultCode: Int, data: Intent?) {
+ val wasSuccessful =
+ resultCode == BiometricEnrollBase.RESULT_FINISHED || resultCode == Activity.RESULT_OK
+ val gateKeeperPasswordHandle =
+ data?.getExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE) as Long?
+ binding.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
+ }
+
+ /**
+ * Helper function to launch fingerprint enrollment(This should be the default behavior
+ * when a user enters their PIN/PATTERN/PASS and no fingerprints are enrolled.
+ */
+ private fun launchFullFingerprintEnrollment(
+ userId: Int,
+ gateKeeperPasswordHandle: Long?,
+ challenge: Long?,
+ challengeToken: ByteArray?,
+ ) {
+ val intent = Intent()
+ intent.setClassName(
+ Utils.SETTINGS_PACKAGE_NAME, FingerprintEnrollIntroductionInternal::class.java.name
+ )
+ intent.putExtra(BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY, true)
+ intent.putExtra(
+ SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
+ SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE
+ )
+
+ intent.putExtra(Intent.EXTRA_USER_ID, userId)
+
+ if (gateKeeperPasswordHandle != null) {
+ intent.putExtra(
+ ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle
+ )
+ } else {
+ intent.putExtra(
+ ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken
+ )
+ intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge)
+ }
+ launchFirstEnrollmentListener.launch(intent)
+ }
+
+ private fun setResultExternal(resultCode: Int) {
+ setResult(resultCode)
+ }
+
+ /** Helper to launch an add fingerprint request */
+ private fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?) {
+ val intent = Intent()
+ intent.setClassName(
+ Utils.SETTINGS_PACKAGE_NAME, FingerprintEnrollEnrolling::class.qualifiedName.toString()
+ )
+ intent.putExtra(Intent.EXTRA_USER_ID, userId)
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken)
+ launchAdditionalFingerprintListener.launch(intent)
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
new file mode 100644
index 0000000..6cddb24
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/**
+ * Models the UI state for fingerprint settings.
+ */
+class FingerprintSettingsViewModel(
+ private val userId: Int,
+ gateKeeperPassword: Long?,
+ theChallenge: Long?,
+ theChallengeToken: ByteArray?,
+ private val fingerprintManager: FingerprintManager
+) : ViewModel() {
+
+ private val _nextStep: MutableStateFlow<NextStepViewModel?> = MutableStateFlow(null)
+ /**
+ * This flow represents the high level state for the FingerprintSettingsV2Fragment. The
+ * consumer of this flow should call [onUiCommandExecuted] which will set the state to null,
+ * confirming that the UI has consumed the last command and is ready to consume another
+ * command.
+ */
+ val nextStep = _nextStep.asStateFlow()
+
+
+ private var gateKeeperPasswordHandle: Long? = gateKeeperPassword
+ private var challenge: Long? = theChallenge
+ private var challengeToken: ByteArray? = theChallengeToken
+
+ /**
+ * Indicates to the view model that a confirm device credential action has been completed
+ * with a [theGateKeeperPasswordHandle] which will be used for [FingerprintManager]
+ * operations such as [FingerprintManager.enroll].
+ */
+ fun onConfirmDevice(wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?) {
+
+ if (!wasSuccessful) {
+ launchFinishSettings("ConfirmDeviceCredential was unsuccessful")
+ return
+ }
+ if (theGateKeeperPasswordHandle == null) {
+ launchFinishSettings("ConfirmDeviceCredential gatekeeper password was null")
+ return
+ }
+
+ gateKeeperPasswordHandle = theGateKeeperPasswordHandle
+ launchEnrollNextStep()
+ }
+
+ /**
+ * Notifies that enrollment was successful.
+ */
+ fun onEnrollSuccess() {
+ _nextStep.update {
+ ShowSettings(userId)
+ }
+ }
+
+ /**
+ * Notifies that an additional enrollment failed.
+ */
+ fun onEnrollAdditionalFailure() {
+ launchFinishSettings("Failed to enroll additional fingerprint")
+ }
+
+ /**
+ * Notifies that the first enrollment failed.
+ */
+ fun onEnrollFirstFailure(reason: String) {
+ launchFinishSettings(reason)
+ }
+
+ /**
+ * Notifies that first enrollment failed (with resultCode)
+ */
+ fun onEnrollFirstFailure(reason: String, resultCode: Int) {
+ launchFinishSettings(reason, resultCode)
+ }
+
+ /**
+ * Notifies that a users first enrollment succeeded.
+ */
+ fun onEnrollFirst(token: ByteArray?, keyChallenge: Long?) {
+ if (token == null) {
+ launchFinishSettings("Error, empty token")
+ return
+ }
+ if (keyChallenge == null) {
+ launchFinishSettings("Error, empty keyChallenge")
+ return
+ }
+ challengeToken = token
+ challenge = keyChallenge
+
+ _nextStep.update {
+ ShowSettings(userId)
+ }
+ }
+
+
+ /**
+ * Indicates if this settings activity has been called with correct token and challenge
+ * and that we do not need to launch confirm device credential.
+ */
+ fun updateTokenAndChallenge(token: ByteArray?, theChallenge: Long?) {
+ challengeToken = token
+ challenge = theChallenge
+ if (challengeToken == null) {
+ _nextStep.update {
+ LaunchConfirmDeviceCredential(userId)
+ }
+ } else {
+ launchEnrollNextStep()
+ }
+ }
+
+ /**
+ * Indicates a UI command has been consumed by the UI, and the logic can send another
+ * UI command.
+ */
+ fun onUiCommandExecuted() {
+ _nextStep.update {
+ null
+ }
+ }
+
+ private fun launchEnrollNextStep() {
+ if (fingerprintManager.getEnrolledFingerprints(userId).isEmpty()) {
+ _nextStep.update {
+ EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, challenge, challengeToken)
+ }
+ } else {
+ _nextStep.update {
+ ShowSettings(userId)
+ }
+ }
+ }
+
+ private fun launchFinishSettings(reason: String) {
+ _nextStep.update {
+ FinishSettings(reason)
+ }
+ }
+
+ private fun launchFinishSettings(reason: String, errorCode: Int) {
+ _nextStep.update {
+ FinishSettingsWithResult(errorCode, reason)
+ }
+ }
+
+ class FingerprintSettingsViewModelFactory(
+ private val userId: Int,
+ private val fingerprintManager: FingerprintManager,
+ ) : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ modelClass: Class<T>,
+ ): T {
+
+ return FingerprintSettingsViewModel(
+ userId, null, null, null, fingerprintManager
+ ) as T
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/NextStepViewModel.kt
new file mode 100644
index 0000000..1046f51
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/NextStepViewModel.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.viewmodel
+
+/**
+ * A class to represent a next step for FingerprintSettings. This is typically to perform an action
+ * such that launches another activity such as EnrollFirstFingerprint() or
+ * LaunchConfirmDeviceCredential().
+ */
+sealed class NextStepViewModel
+
+data class EnrollFirstFingerprint(
+ val userId: Int, val gateKeeperPasswordHandle: Long?,
+ val challenge: Long?,
+ val challengeToken: ByteArray?,
+) : NextStepViewModel()
+
+data class EnrollAdditionalFingerprint(
+ val userId: Int,
+ val challengeToken: ByteArray?,
+) : NextStepViewModel()
+
+data class FinishSettings(
+ val reason: String
+) : NextStepViewModel()
+
+data class FinishSettingsWithResult(
+ val result: Int, val reason: String
+) : NextStepViewModel()
+
+data class ShowSettings(val userId: Int) : NextStepViewModel()
+
+data class LaunchConfirmDeviceCredential(val userId: Int) : NextStepViewModel()
+
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index b5d12a5..3100706 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -72,6 +72,7 @@
import com.android.settings.biometrics.combination.CombinedBiometricSettings;
import com.android.settings.biometrics.face.FaceSettings;
import com.android.settings.biometrics.fingerprint.FingerprintSettings;
+import com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment;
import com.android.settings.bluetooth.BluetoothBroadcastDialog;
import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
import com.android.settings.bluetooth.BluetoothFindBroadcastsFragment;
@@ -266,6 +267,7 @@
AssistGestureSettings.class.getName(),
FaceSettings.class.getName(),
FingerprintSettings.FingerprintSettingsFragment.class.getName(),
+ FingerprintSettingsV2Fragment.class.getName(),
CombinedBiometricSettings.class.getName(),
CombinedBiometricProfileSettings.class.getName(),
SwipeToNotificationSettings.class.getName(),