Remove FingerprintStateViewModel.

Since FingerprintStateViewModel is too general as a view model, this CL
removes it and adds more concrete flows in FingerprintSettingsViewModel
and FingerprintEnrollViewModel.

Test: atest FingerprintManagerInteractorTest
Test: atest FingerprintSettingsViewModelTest
Test: Verified enroll/deletion/renaming/authentication flows on Settings

Change-Id: I3a0662195c4989de0813b92bccda9d36a7f7e32a
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
index e7c458d..1f57198 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
@@ -47,6 +47,12 @@
   /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
   val maxEnrollableFingerprints: Flow<Int>
 
+  /** Returns true if a user can enroll a fingerprint false otherwise. */
+  val canEnrollFingerprints: Flow<Boolean>
+
+  /** Retrieves the sensor properties of a device */
+  val sensorPropertiesInternal: Flow<FingerprintSensorPropertiesInternal?>
+
   /** Runs [FingerprintManager.authenticate] */
   suspend fun authenticate(): FingerprintAuthAttemptViewModel
 
@@ -60,9 +66,6 @@
    */
   suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
 
-  /** Returns true if a user can enroll a fingerprint false otherwise. */
-  fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean>
-
   /**
    * Removes the given fingerprint, returning true if it was successfully removed and false
    * otherwise
@@ -77,9 +80,6 @@
 
   /** Indicates if the press to auth feature has been enabled */
   suspend fun pressToAuthEnabled(): Boolean
-
-  /** Retrieves the sensor properties of a device */
-  suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal>
 }
 
 class FingerprintManagerInteractorImpl(
@@ -120,8 +120,15 @@
     )
   }
 
-  override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
-    emit(numFingerprints < maxFingerprints)
+  override val canEnrollFingerprints: Flow<Boolean> = flow {
+    emit(
+      fingerprintManager.getEnrolledFingerprints(applicationContext.userId).size < maxFingerprints
+    )
+  }
+
+  override val sensorPropertiesInternal = flow {
+    val sensorPropertiesInternal = fingerprintManager.sensorPropertiesInternal
+    emit(if (sensorPropertiesInternal.isEmpty()) null else sensorPropertiesInternal.first())
   }
 
   override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
@@ -165,11 +172,6 @@
     it.resume(pressToAuthProvider())
   }
 
-  override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
-    suspendCancellableCoroutine {
-      it.resume(fingerprintManager.sensorPropertiesInternal)
-    }
-
   override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
     suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
       val authenticationCallback =
diff --git a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
index 36bdf8d..db28e79 100644
--- a/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/shared/model/FingerprintViewModel.kt
@@ -16,18 +16,6 @@
 
 package com.android.settings.biometrics.fingerprint2.shared.model
 
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-
-/** Represents the fingerprint data nad the relevant state. */
-data class FingerprintStateViewModel(
-  val fingerprintViewModels: List<FingerprintViewModel>,
-  val canEnroll: Boolean,
-  val maxFingerprints: Int,
-  val hasSideFps: Boolean,
-  val pressToAuth: Boolean,
-  val sensorProps: FingerprintSensorPropertiesInternal,
-)
-
 data class FingerprintViewModel(
   val name: String,
   val fingerId: Int,
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 d497d46..f6d20ae 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
@@ -47,10 +47,10 @@
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
@@ -179,8 +179,10 @@
       )[FingerprintEnrollmentNavigationViewModel::class.java]
 
     // Initialize FingerprintViewModel
-    ViewModelProvider(this, FingerprintViewModel.FingerprintViewModelFactory(interactor))[
-      FingerprintViewModel::class.java]
+    ViewModelProvider(
+      this,
+      FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor)
+    )[FingerprintEnrollViewModel::class.java]
 
     // Initialize scroll view model
     ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt
index c7fcb66..ebbdef8 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollmentIntroV2Fragment.kt
@@ -34,10 +34,10 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
-import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintViewModel
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
 import com.google.android.setupcompat.template.FooterBarMixin
 import com.google.android.setupcompat.template.FooterButton
@@ -76,7 +76,7 @@
   private lateinit var footerBarMixin: FooterBarMixin
   private lateinit var textModel: TextModel
   private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
-  private lateinit var fingerprintStateViewModel: FingerprintViewModel
+  private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
   private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
   private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
 
@@ -84,8 +84,8 @@
     super.onCreate(savedInstanceState)
     navigationViewModel =
       ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
-    fingerprintStateViewModel =
-      ViewModelProvider(requireActivity())[FingerprintViewModel::class.java]
+    fingerprintEnrollViewModel =
+      ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
     fingerprintScrollViewModel =
       ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
     gateKeeperViewModel =
@@ -98,13 +98,11 @@
     lifecycleScope.launch {
       combine(
           navigationViewModel.enrollType,
-          fingerprintStateViewModel.fingerprintStateViewModel,
-        ) { enrollType, fingerprintStateViewModel ->
-          Pair(enrollType, fingerprintStateViewModel)
+          fingerprintEnrollViewModel.sensorType,
+        ) { enrollType, sensorType ->
+          Pair(enrollType, sensorType)
         }
-        .collect { (enrollType, fingerprintStateViewModel) ->
-          val sensorProps = fingerprintStateViewModel?.sensorProps
-
+        .collect { (enrollType, sensorType) ->
           textModel =
             when (enrollType) {
               Unicorn -> getUnicornTextModel()
@@ -145,7 +143,7 @@
 
             val iconShield: ImageView = view.requireViewById(R.id.icon_shield)
             val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6)
-            when (sensorProps?.sensorType) {
+            when (sensorType) {
               FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
               FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> {
                 footerMessage6.visibility = View.VISIBLE
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
new file mode 100644
index 0000000..879f425
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
+
+import android.hardware.fingerprint.FingerprintSensorProperties
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.transform
+
+/** Represents all of the fingerprint information needed for fingerprint enrollment. */
+class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
+  ViewModel() {
+
+  /** Represents the stream of [FingerprintSensorProperties.SensorType] */
+  val sensorType: Flow<Int> =
+    fingerprintManagerInteractor.sensorPropertiesInternal.transform { it?.sensorType }
+
+  class FingerprintEnrollViewModelFactory(val interactor: FingerprintManagerInteractor) :
+    ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+      return FingerprintEnrollViewModel(interactor) as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintStateViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintStateViewModel.kt
deleted file mode 100644
index 20e3a0a..0000000
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintStateViewModel.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
-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 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 {
-          com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel(
-            it.name,
-            it.fingerId,
-            it.deviceId
-          )
-        }
-      val sensorProps = fingerprintManagerInteractor.sensorPropertiesInternal().first()
-      val maxFingerprints = 5
-      _fingerprintViewModel.update {
-        FingerprintStateViewModel(
-          enrolledFingerprints,
-          enrolledFingerprints.size < maxFingerprints,
-          maxFingerprints,
-          sensorProps.isAnySidefpsType,
-          false,
-          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/ui/settings/binder/FingerprintSettingsViewBinder.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
index 6a44630..9f42d81 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/binder/FingerprintSettingsViewBinder.kt
@@ -20,7 +20,6 @@
 import android.util.Log
 import androidx.lifecycle.LifecycleCoroutineScope
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
 import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
@@ -35,6 +34,7 @@
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.launch
@@ -70,7 +70,11 @@
     /** Indicates what result should be set for the returning callee */
     fun setResultExternal(resultCode: Int)
     /** Indicates the settings UI should be shown */
-    fun showSettings(state: FingerprintStateViewModel)
+    fun showSettings(enrolledFingerprints: List<FingerprintViewModel>)
+    /** Updates the add fingerprints preference */
+    fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int)
+    /** Updates the sfps fingerprints preference */
+    fun updateSfpsPreference(isSfpsPrefVisible: Boolean)
     /** Indicates that a user has been locked out */
     fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
     /** Indicates a fingerprint preference should be highlighted */
@@ -93,9 +97,13 @@
     /** Result listener for launching enrollments **after** a user has reached the settings page. */
 
     // Settings display flow
+    lifecycleScope.launch { viewModel.enrolledFingerprints.collect { view.showSettings(it) } }
     lifecycleScope.launch {
-      viewModel.fingerprintState.filterNotNull().collect { view.showSettings(it) }
+      viewModel.addFingerprintPrefInfo.collect { (enablePref, maxFingerprints) ->
+        view.updateAddFingerprintsPreference(enablePref, maxFingerprints)
+      }
     }
+    lifecycleScope.launch { viewModel.isSfpsPrefVisible.collect { view.updateSfpsPreference(it) } }
 
     // Dialog flow
     lifecycleScope.launch {
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
index 7dcf46a..c818566 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
@@ -47,7 +47,6 @@
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
 import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
 import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
@@ -304,44 +303,52 @@
     settingsViewModel.onDeleteClicked(fingerprintViewModel)
   }
 
-  override fun showSettings(state: FingerprintStateViewModel) {
+  override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) {
     val category =
       this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
         as PreferenceCategory?
 
     category?.removeAll()
 
-    state.fingerprintViewModels.forEach { fingerprint ->
+    enrolledFingerprints.forEach { fingerprint ->
       category?.addPreference(
         FingerprintSettingsPreference(
           requireContext(),
           fingerprint,
           this@FingerprintSettingsV2Fragment,
-          state.fingerprintViewModels.size == 1,
+          enrolledFingerprints.size == 1,
         )
       )
     }
     category?.isVisible = true
-
-    createFingerprintsFooterPreference(state.canEnroll, state.maxFingerprints)
     preferenceScreen.isVisible = true
+    addFooter()
+  }
 
+  override fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int) {
+    val pref = this@FingerprintSettingsV2Fragment.findPreference<Preference>(KEY_FINGERPRINT_ADD)
+    val maxSummary = context?.getString(R.string.fingerprint_add_max, maxFingerprints) ?: ""
+    pref?.summary = maxSummary
+    pref?.isEnabled = canEnroll
+    pref?.setOnPreferenceClickListener {
+      navigationViewModel.onAddFingerprintClicked()
+      true
+    }
+    pref?.isVisible = true
+  }
+
+  override fun updateSfpsPreference(isSfpsPrefVisible: Boolean) {
     val sideFpsPref =
       this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_CATEGORY)
         as PreferenceCategory?
-    sideFpsPref?.isVisible = false
-
-    if (state.hasSideFps) {
-      sideFpsPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
-      val otherPref =
-        this@FingerprintSettingsV2Fragment.findPreference(
-          KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH
-        ) as Preference?
-      otherPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
-    }
-    addFooter(state.hasSideFps)
+    sideFpsPref?.isVisible = isSfpsPrefVisible
+    val otherPref =
+      this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH)
+        as Preference?
+    otherPref?.isVisible = isSfpsPrefVisible
   }
-  private fun addFooter(hasSideFps: Boolean) {
+
+  private fun addFooter() {
     val footer =
       this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_FOOTER)
         as PreferenceCategory?
@@ -380,10 +387,8 @@
       footerColumns.add(column1)
       val column2 = FooterColumn()
       column2.title = getText(R.string.security_fingerprint_disclaimer_lockscreen_disabled_2)
-      if (hasSideFps) {
-        column2.learnMoreOverrideText =
-          getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
-      }
+      column2.learnMoreOverrideText =
+        getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
       column2.learnMoreOnClickListener = learnMoreClickListener
       footerColumns.add(column2)
     } else {
@@ -394,10 +399,8 @@
           DeviceHelper.getDeviceName(requireActivity())
         )
       column.learnMoreOnClickListener = learnMoreClickListener
-      if (hasSideFps) {
-        column.learnMoreOverrideText =
-          getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
-      }
+      column.learnMoreOverrideText =
+        getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
       footerColumns.add(column)
     }
 
@@ -550,18 +553,6 @@
     }
   }
 
-  private fun createFingerprintsFooterPreference(canEnroll: Boolean, maxFingerprints: Int) {
-    val pref = this@FingerprintSettingsV2Fragment.findPreference<Preference>(KEY_FINGERPRINT_ADD)
-    val maxSummary = context?.getString(R.string.fingerprint_add_max, maxFingerprints) ?: ""
-    pref?.summary = maxSummary
-    pref?.isEnabled = canEnroll
-    pref?.setOnPreferenceClickListener {
-      navigationViewModel.onAddFingerprintClicked()
-      true
-    }
-    pref?.isVisible = true
-  }
-
   private fun fingerprintPreferences(): List<FingerprintSettingsPreference?> {
     val category =
       this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
index fbd0f1d..64d8a12 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
@@ -18,26 +18,27 @@
 
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 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 com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
-import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintStateViewModel
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combineTransform
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.last
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.sample
+import kotlinx.coroutines.flow.transform
 import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
@@ -52,13 +53,30 @@
   private val backgroundDispatcher: CoroutineDispatcher,
   private val navigationViewModel: FingerprintSettingsNavigationViewModel,
 ) : ViewModel() {
-
-  private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
-  private val fingerprintSensorPropertiesInternal:
-    MutableStateFlow<List<FingerprintSensorPropertiesInternal>?> =
+  private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> =
     MutableStateFlow(null)
 
+  /** Represents the stream of enrolled fingerprints. */
+  val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
+    _enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown()
+
+  /** Represents the stream of the information of "Add Fingerprint" preference. */
+  val addFingerprintPrefInfo: Flow<Pair<Boolean, Int>> =
+    _enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
+      emit(
+        Pair(
+          fingerprintManagerInteractor.canEnrollFingerprints.first(),
+          fingerprintManagerInteractor.maxEnrollableFingerprints.first()
+        )
+      )
+    }
+
+  /** Represents the stream of visibility of sfps preference. */
+  val isSfpsPrefVisible: Flow<Boolean> =
+    _enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
+      emit(fingerprintManagerInteractor.hasSideFps() && !it.isNullOrEmpty())
+    }
+
   private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
   val isShowingDialog =
     _isShowingDialog.combine(navigationViewModel.nextStep) { dialogFlow, nextStep ->
@@ -69,16 +87,13 @@
       }
     }
 
-  private val _fingerprintStateViewModel: MutableStateFlow<FingerprintStateViewModel?> =
-    MutableStateFlow(null)
-  val fingerprintState: Flow<FingerprintStateViewModel?> =
-    _fingerprintStateViewModel.combineTransform(navigationViewModel.nextStep) {
-      settingsShowingViewModel,
-      currStep ->
-      if (currStep != null && currStep is ShowSettings) {
-        emit(settingsShowingViewModel)
-      }
-    }
+  private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+  private val _fingerprintSensorType: Flow<Int> =
+    fingerprintManagerInteractor.sensorPropertiesInternal.transform { it?.sensorType }
+
+  private val _sensorNullOrEmpty: Flow<Boolean> =
+    fingerprintManagerInteractor.sensorPropertiesInternal.map{it ==null}
 
   private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
     MutableStateFlow(null)
@@ -86,7 +101,7 @@
   private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
     MutableSharedFlow()
 
-  private val attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
+  private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
   /**
    * This is a very tricky flow. The current fingerprint manager APIs are not robust, and a proper
    * implementation would take quite a lot of code to implement, it might be easier to rewrite
@@ -101,11 +116,20 @@
         _isShowingDialog,
         navigationViewModel.nextStep,
         _consumerShouldAuthenticate,
-        _fingerprintStateViewModel,
+        _enrolledFingerprints,
         _isLockedOut,
-        attemptsSoFar,
-        fingerprintSensorPropertiesInternal
-      ) { dialogShowing, step, resume, fingerprints, isLockedOut, attempts, sensorProps ->
+        _attemptsSoFar,
+        _fingerprintSensorType,
+        _sensorNullOrEmpty
+      ) {
+        dialogShowing,
+        step,
+        resume,
+        fingerprints,
+        isLockedOut,
+        attempts,
+        sensorType,
+        sensorNullOrEmpty ->
         if (DEBUG) {
           Log.d(
             TAG,
@@ -115,13 +139,13 @@
               "fingerprints=${fingerprints}," +
               "lockedOut=${isLockedOut}," +
               "attempts=${attempts}," +
-              "sensorProps=${sensorProps}"
+              "sensorType=${sensorType}" +
+              "sensorNullOrEmpty=${sensorNullOrEmpty}"
           )
         }
-        if (sensorProps.isNullOrEmpty()) {
+        if (sensorNullOrEmpty) {
           return@combine false
         }
-        val sensorType = sensorProps[0].sensorType
         if (
           listOf(
               FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
@@ -133,7 +157,7 @@
         }
 
         if (step != null && step is ShowSettings) {
-          if (fingerprints?.fingerprintViewModels?.isNotEmpty() == true) {
+          if (fingerprints?.isNotEmpty() == true) {
             return@combine dialogShowing == null && isLockedOut == null && resume && attempts < 15
           }
         }
@@ -173,17 +197,11 @@
 
   init {
     viewModelScope.launch {
-      fingerprintSensorPropertiesInternal.update {
-        fingerprintManagerInteractor.sensorPropertiesInternal()
-      }
-    }
-
-    viewModelScope.launch {
       navigationViewModel.nextStep.filterNotNull().collect {
         _isShowingDialog.update { null }
         if (it is ShowSettings) {
           // reset state
-          updateSettingsData()
+          updateEnrolledFingerprints()
         }
       }
     }
@@ -200,7 +218,7 @@
   }
 
   override fun toString(): String {
-    return "userId: $userId\n" + "fingerprintState: ${_fingerprintStateViewModel.value}\n"
+    return "userId: $userId\n" + "enrolledFingerprints: ${_enrolledFingerprints.value}\n"
   }
 
   /** The fingerprint delete button has been clicked. */
@@ -229,7 +247,7 @@
   fun deleteFingerprint(fp: FingerprintViewModel) {
     viewModelScope.launch(backgroundDispatcher) {
       if (fingerprintManagerInteractor.removeFingerprint(fp)) {
-        updateSettingsData()
+        updateEnrolledFingerprints()
       }
     }
   }
@@ -238,45 +256,25 @@
   fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
     viewModelScope.launch {
       fingerprintManagerInteractor.renameFingerprint(fp, newName)
-      updateSettingsData()
+      updateEnrolledFingerprints()
     }
   }
 
   private fun attemptingAuth() {
-    attemptsSoFar.update { it + 1 }
+    _attemptsSoFar.update { it + 1 }
   }
 
   private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
     _authSucceeded.emit(success)
-    attemptsSoFar.update { 0 }
+    _attemptsSoFar.update { 0 }
   }
 
   private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
     _isLockedOut.update { attemptViewModel }
   }
 
-  /**
-   * This function is sort of a hack, it's used whenever we want to check for fingerprint state
-   * updates.
-   */
-  private suspend fun updateSettingsData() {
-    Log.d(TAG, "update settings data called")
-    val fingerprints = fingerprintManagerInteractor.enrolledFingerprints.last()
-    val canEnrollFingerprint =
-      fingerprintManagerInteractor.canEnrollFingerprints(fingerprints.size).last()
-    val maxFingerprints = fingerprintManagerInteractor.maxEnrollableFingerprints.last()
-    val hasSideFps = fingerprintManagerInteractor.hasSideFps()
-    val pressToAuthEnabled = fingerprintManagerInteractor.pressToAuthEnabled()
-    _fingerprintStateViewModel.update {
-      FingerprintStateViewModel(
-        fingerprints,
-        canEnrollFingerprint,
-        maxFingerprints,
-        hasSideFps,
-        pressToAuthEnabled,
-        fingerprintManagerInteractor.sensorPropertiesInternal().first(),
-      )
-    }
+  private suspend fun updateEnrolledFingerprints() {
+    _enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() }
   }
 
   /** Used to indicate whether the consumer of the view model is ready for authentication. */
@@ -284,6 +282,13 @@
     _consumerShouldAuthenticate.update { authenticate }
   }
 
+  private fun <T> Flow<T>.filterOnlyWhenSettingsIsShown() =
+    combineTransform(navigationViewModel.nextStep) { value, currStep ->
+      if (currStep != null && currStep is ShowSettings) {
+        emit(value)
+      }
+    }
+
   class FingerprintSettingsViewModelFactory(
     private val userId: Int,
     private val interactor: FingerprintManagerInteractor,
@@ -307,7 +312,7 @@
   }
 }
 
-private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+private inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
   flow: Flow<T1>,
   flow2: Flow<T2>,
   flow3: Flow<T3>,
@@ -315,9 +320,10 @@
   flow5: Flow<T5>,
   flow6: Flow<T6>,
   flow7: Flow<T7>,
-  crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+  flow8: Flow<T8>,
+  crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
 ): Flow<R> {
-  return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
+  return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> ->
     @Suppress("UNCHECKED_CAST")
     transform(
       args[0] as T1,
@@ -327,6 +333,7 @@
       args[4] as T5,
       args[5] as T6,
       args[6] as T7,
+      args[7] as T8,
     )
   }
 }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
index 759306e..e2bdd17 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
@@ -23,7 +23,7 @@
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
 import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
 
 /** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
 class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
@@ -53,15 +53,16 @@
   override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
     return challengeToGenerate
   }
-  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
-    emit(enrolledFingerprintsInternal)
-  }
+  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
+    flowOf(enrolledFingerprintsInternal)
 
-  override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
-    emit(numFingerprints < enrollableFingerprints)
-  }
+  override val canEnrollFingerprints: Flow<Boolean> =
+    flowOf(enrolledFingerprintsInternal.size < enrollableFingerprints)
 
-  override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
+  override val sensorPropertiesInternal: Flow<FingerprintSensorPropertiesInternal?> =
+    flowOf(sensorProps.first())
+
+  override val maxEnrollableFingerprints: Flow<Int> = flowOf(enrollableFingerprints)
 
   override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
     return enrolledFingerprintsInternal.remove(fp)
@@ -80,7 +81,4 @@
   override suspend fun pressToAuthEnabled(): Boolean {
     return pressToAuthEnabled
   }
-
-  override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
-    sensorProps
 }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
index cc6f42a..70943f0 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.Intent
-import android.content.res.Resources
 import android.hardware.fingerprint.Fingerprint
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintManager.CryptoObject
@@ -51,8 +50,11 @@
 import org.mockito.ArgumentMatchers.nullable
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoJUnitRunner
+import org.mockito.stubbing.OngoingStubbing
 
 @RunWith(MockitoJUnitRunner::class)
 class FingerprintManagerInteractorTest {
@@ -82,8 +84,7 @@
   @Test
   fun testEmptyFingerprints() =
     testScope.runTest {
-      Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
-        .thenReturn(emptyList())
+      whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
 
       val emptyFingerprintList: List<Fingerprint> = emptyList()
       assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
@@ -94,8 +95,7 @@
     testScope.runTest {
       val expected = Fingerprint("Finger 1,", 2, 3L)
       val fingerprintList: List<Fingerprint> = listOf(expected)
-      Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
-        .thenReturn(fingerprintList)
+      whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
 
       val list = underTest.enrolledFingerprints.last()
       assertThat(list.size).isEqualTo(fingerprintList.size)
@@ -108,21 +108,22 @@
   @Test
   fun testCanEnrollFingerprint() =
     testScope.runTest {
-      val mockContext = Mockito.mock(Context::class.java)
-      val resources = Mockito.mock(Resources::class.java)
-      Mockito.`when`(mockContext.resources).thenReturn(resources)
-      Mockito.`when`(resources.getInteger(anyInt())).thenReturn(3)
-      underTest =
-        FingerprintManagerInteractorImpl(
-          mockContext,
-          backgroundDispatcher,
-          fingerprintManager,
-          gateKeeperPasswordProvider,
-          pressToAuthProvider,
+      val fingerprintList1: List<Fingerprint> =
+        listOf(
+          Fingerprint("Finger 1,", 2, 3L),
+          Fingerprint("Finger 2,", 3, 3L),
+          Fingerprint("Finger 3,", 4, 3L)
         )
+      val fingerprintList2: List<Fingerprint> =
+        fingerprintList1.plus(
+          listOf(Fingerprint("Finger 4,", 5, 3L), Fingerprint("Finger 5,", 6, 3L))
+        )
+      whenever(fingerprintManager.getEnrolledFingerprints(anyInt()))
+        .thenReturn(fingerprintList1)
+        .thenReturn(fingerprintList2)
 
-      assertThat(underTest.canEnrollFingerprints(2).last()).isTrue()
-      assertThat(underTest.canEnrollFingerprints(3).last()).isFalse()
+      assertThat(underTest.canEnrollFingerprints.last()).isTrue()
+      assertThat(underTest.canEnrollFingerprints.last()).isFalse()
     }
 
   @Test
@@ -132,7 +133,7 @@
       val challenge = 100L
       val intent = Intent()
       intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge)
-      Mockito.`when`(
+      whenever(
           gateKeeperPasswordProvider.requestGatekeeperHat(
             any(Intent::class.java),
             anyLong(),
@@ -148,8 +149,7 @@
       val job = testScope.launch { result = underTest.generateChallenge(1L) }
       runCurrent()
 
-      Mockito.verify(fingerprintManager)
-        .generateChallenge(anyInt(), capture(generateChallengeCallback))
+      verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback))
       generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge)
 
       runCurrent()
@@ -173,7 +173,7 @@
         testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
       runCurrent()
 
-      Mockito.verify(fingerprintManager)
+      verify(fingerprintManager)
         .remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
       removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1)
 
@@ -197,7 +197,7 @@
         testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
       runCurrent()
 
-      Mockito.verify(fingerprintManager)
+      verify(fingerprintManager)
         .remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
       removalCallback.value.onRemovalError(
         fingerprintToRemove,
@@ -218,8 +218,7 @@
 
       underTest.renameFingerprint(fingerprintToRename, "Woo")
 
-      Mockito.verify(fingerprintManager)
-        .rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
+      verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
     }
 
   @Test
@@ -235,7 +234,7 @@
 
       runCurrent()
 
-      Mockito.verify(fingerprintManager)
+      verify(fingerprintManager)
         .authenticate(
           nullable(CryptoObject::class.java),
           any(CancellationSignal::class.java),
@@ -263,7 +262,7 @@
 
       runCurrent()
 
-      Mockito.verify(fingerprintManager)
+      verify(fingerprintManager)
         .authenticate(
           nullable(CryptoObject::class.java),
           any(CancellationSignal::class.java),
@@ -284,4 +283,5 @@
   private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
   private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
   private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+  private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
 }