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)
}