diff --git a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml
new file mode 100644
index 00000000000..32df66592f3
--- /dev/null
+++ b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 d26b812a686..70d58eab776 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
@@ -52,6 +52,8 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.Finge
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.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollConfirmationViewModel
@@ -100,6 +102,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
private lateinit var fingerprintEnrollConfirmationViewModel:
FingerprintEnrollConfirmationViewModel
+ private lateinit var udfpsViewModel: UdfpsViewModel
private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */
@@ -306,6 +309,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
),
)[RFPSViewModel::class.java]
+ udfpsViewModel =
+ ViewModelProvider(
+ this,
+ UdfpsViewModel.UdfpsEnrollmentFactory(),
+ )[UdfpsViewModel::class.java]
+
fingerprintEnrollConfirmationViewModel =
ViewModelProvider(
this,
@@ -344,6 +353,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
is Enrollment -> {
when (step.sensor.sensorType) {
FingerprintSensorType.REAR -> RFPSEnrollFragment()
+ FingerprintSensorType.UDFPS_OPTICAL,
+ FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFragment()
else -> FingerprintEnrollEnrollingV2Fragment()
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt
new file mode 100644
index 00000000000..61451287dc6
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 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.udfps.ui.fragment
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieCompositionFactory
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
+import com.google.android.setupdesign.GlifLayout
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enrolling) {
+
+ /** Used for testing purposes */
+ private var factory: ViewModelProvider.Factory? = null
+ private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] }
+
+ @VisibleForTesting
+ constructor(theFactory: ViewModelProvider.Factory) : this() {
+ factory = theFactory
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val layout = view as GlifLayout
+ val illustrationLottie: LottieAnimationView = layout.findViewById(R.id.illustration_lottie)!!
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewModel.stageFlow.collect {
+ layout.setHeaderText(getHeaderText(it))
+ getDescriptionText(it)?.let { descriptionText ->
+ layout.setDescriptionText(descriptionText)
+ }
+ getLottie(it)?.let { lottie ->
+ layout.descriptionText = ""
+ LottieCompositionFactory.fromRawRes(requireContext().applicationContext, lottie)
+ .addListener { comp ->
+ comp?.let { composition ->
+ viewLifecycleOwner.lifecycleScope.launch {
+ illustrationLottie.setComposition(composition)
+ illustrationLottie.visibility = View.VISIBLE
+ illustrationLottie.playAnimation()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun getHeaderText(stageViewModel: StageViewModel): Int {
+ return when (stageViewModel) {
+ StageViewModel.Center,
+ StageViewModel.Guided,
+ StageViewModel.Fingertip,
+ StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
+ StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
+ StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
+ }
+ }
+
+ private fun getDescriptionText(stageViewModel: StageViewModel): Int? {
+ return when (stageViewModel) {
+ StageViewModel.Center,
+ StageViewModel.Guided,
+ StageViewModel.Fingertip,
+ StageViewModel.LeftEdge,
+ StageViewModel.RightEdge -> null
+ StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
+ }
+ }
+
+ private fun getLottie(stageViewModel: StageViewModel): Int? {
+ return when (stageViewModel) {
+ StageViewModel.Center,
+ StageViewModel.Guided -> R.raw.udfps_center_hint_lottie
+ StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie
+ StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
+ StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
+ StageViewModel.Unknown -> null
+ }
+ }
+
+ private val viewModelProvider: ViewModelProvider by lazy {
+ if (factory != null) {
+ ViewModelProvider(requireActivity(), factory!!)
+ } else {
+ ViewModelProvider(requireActivity())
+ }
+ }
+
+ companion object {
+ private const val TAG = "UDFPSEnrollFragment"
+ private val navStep = FingerprintNavigationStep.Enrollment::class
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt
new file mode 100644
index 00000000000..b879ce17e7b
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.udfps.ui.viewmodel
+
+/**
+ * A view model that describes the various stages of UDFPS Enrollment. This stages typically update
+ * the enrollment UI in a major way, such as changing the lottie animation or changing the location
+ * of the where the user should press their fingerprint
+ */
+sealed class StageViewModel {
+ data object Unknown : StageViewModel()
+
+ data object Guided : StageViewModel()
+
+ data object Center : StageViewModel()
+
+ data object Fingertip : StageViewModel()
+
+ data object LeftEdge : StageViewModel()
+
+ data object RightEdge : StageViewModel()
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
new file mode 100644
index 00000000000..4fc3d1c5446
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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.udfps.ui.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
+import kotlinx.coroutines.flow.flowOf
+
+/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
+class UdfpsViewModel() : ViewModel() {
+
+ /** Indicates what stage UDFPS enrollment is in. */
+ val stageFlow = flowOf(StageViewModel.Center)
+
+ class UdfpsEnrollmentFactory() : ViewModelProvider.Factory {
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ return UdfpsViewModel() as T
+ }
+ }
+
+ companion object {
+ private val navStep = FingerprintNavigationStep.Enrollment::class
+ private const val TAG = "UDFPSViewModel"
+ }
+}