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
This commit is contained in:
Hao Dong
2023-08-31 01:30:40 +00:00
parent 4fda9e8e42
commit 0978271198
11 changed files with 220 additions and 263 deletions

View File

@@ -47,6 +47,12 @@ interface FingerprintManagerInteractor {
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */ /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
val maxEnrollableFingerprints: Flow<Int> 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] */ /** Runs [FingerprintManager.authenticate] */
suspend fun authenticate(): FingerprintAuthAttemptViewModel suspend fun authenticate(): FingerprintAuthAttemptViewModel
@@ -60,9 +66,6 @@ interface FingerprintManagerInteractor {
*/ */
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> 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 * Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise * otherwise
@@ -77,9 +80,6 @@ interface FingerprintManagerInteractor {
/** Indicates if the press to auth feature has been enabled */ /** Indicates if the press to auth feature has been enabled */
suspend fun pressToAuthEnabled(): Boolean suspend fun pressToAuthEnabled(): Boolean
/** Retrieves the sensor properties of a device */
suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal>
} }
class FingerprintManagerInteractorImpl( class FingerprintManagerInteractorImpl(
@@ -120,8 +120,15 @@ class FingerprintManagerInteractorImpl(
) )
} }
override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow { override val canEnrollFingerprints: Flow<Boolean> = flow {
emit(numFingerprints < maxFingerprints) 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) } override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
@@ -165,11 +172,6 @@ class FingerprintManagerInteractorImpl(
it.resume(pressToAuthProvider()) it.resume(pressToAuthProvider())
} }
override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
suspendCancellableCoroutine {
it.resume(fingerprintManager.sensorPropertiesInternal)
}
override suspend fun authenticate(): FingerprintAuthAttemptViewModel = override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> -> suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
val authenticationCallback = val authenticationCallback =

View File

@@ -16,18 +16,6 @@
package com.android.settings.biometrics.fingerprint2.shared.model 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( data class FingerprintViewModel(
val name: String, val name: String,
val fingerId: Int, val fingerId: Int,

View File

@@ -47,10 +47,10 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.Finge
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation 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.Education
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment 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.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel 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.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.Finish
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
@@ -179,8 +179,10 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
)[FingerprintEnrollmentNavigationViewModel::class.java] )[FingerprintEnrollmentNavigationViewModel::class.java]
// Initialize FingerprintViewModel // Initialize FingerprintViewModel
ViewModelProvider(this, FingerprintViewModel.FingerprintViewModelFactory(interactor))[ ViewModelProvider(
FingerprintViewModel::class.java] this,
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor)
)[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model // Initialize scroll view model
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[ ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[

View File

@@ -34,10 +34,10 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.android.settings.R 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.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel 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.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
import com.google.android.setupcompat.template.FooterBarMixin import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton import com.google.android.setupcompat.template.FooterButton
@@ -76,7 +76,7 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
private lateinit var footerBarMixin: FooterBarMixin private lateinit var footerBarMixin: FooterBarMixin
private lateinit var textModel: TextModel private lateinit var textModel: TextModel
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
private lateinit var fingerprintStateViewModel: FingerprintViewModel private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
@@ -84,8 +84,8 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
navigationViewModel = navigationViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java] ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
fingerprintStateViewModel = fingerprintEnrollViewModel =
ViewModelProvider(requireActivity())[FingerprintViewModel::class.java] ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
fingerprintScrollViewModel = fingerprintScrollViewModel =
ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java] ViewModelProvider(requireActivity())[FingerprintScrollViewModel::class.java]
gateKeeperViewModel = gateKeeperViewModel =
@@ -98,13 +98,11 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
lifecycleScope.launch { lifecycleScope.launch {
combine( combine(
navigationViewModel.enrollType, navigationViewModel.enrollType,
fingerprintStateViewModel.fingerprintStateViewModel, fingerprintEnrollViewModel.sensorType,
) { enrollType, fingerprintStateViewModel -> ) { enrollType, sensorType ->
Pair(enrollType, fingerprintStateViewModel) Pair(enrollType, sensorType)
} }
.collect { (enrollType, fingerprintStateViewModel) -> .collect { (enrollType, sensorType) ->
val sensorProps = fingerprintStateViewModel?.sensorProps
textModel = textModel =
when (enrollType) { when (enrollType) {
Unicorn -> getUnicornTextModel() Unicorn -> getUnicornTextModel()
@@ -145,7 +143,7 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
val iconShield: ImageView = view.requireViewById(R.id.icon_shield) val iconShield: ImageView = view.requireViewById(R.id.icon_shield)
val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6) val footerMessage6: TextView = view.requireViewById(R.id.footer_message_6)
when (sensorProps?.sensorType) { when (sensorType) {
FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC, FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> { FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> {
footerMessage6.visibility = View.VISIBLE footerMessage6.visibility = View.VISIBLE

View File

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

View File

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

View File

@@ -20,7 +20,6 @@ import android.hardware.fingerprint.FingerprintManager
import android.util.Log import android.util.Log
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel 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.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
@@ -35,6 +34,7 @@ import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.Prefer
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.ShowSettings
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -70,7 +70,11 @@ object FingerprintSettingsViewBinder {
/** Indicates what result should be set for the returning callee */ /** Indicates what result should be set for the returning callee */
fun setResultExternal(resultCode: Int) fun setResultExternal(resultCode: Int)
/** Indicates the settings UI should be shown */ /** 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 */ /** Indicates that a user has been locked out */
fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
/** Indicates a fingerprint preference should be highlighted */ /** Indicates a fingerprint preference should be highlighted */
@@ -93,9 +97,13 @@ object FingerprintSettingsViewBinder {
/** Result listener for launching enrollments **after** a user has reached the settings page. */ /** Result listener for launching enrollments **after** a user has reached the settings page. */
// Settings display flow // Settings display flow
lifecycleScope.launch { viewModel.enrolledFingerprints.collect { view.showSettings(it) } }
lifecycleScope.launch { 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 // Dialog flow
lifecycleScope.launch { lifecycleScope.launch {

View File

@@ -47,7 +47,6 @@ import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl 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.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.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
@@ -304,44 +303,52 @@ class FingerprintSettingsV2Fragment :
settingsViewModel.onDeleteClicked(fingerprintViewModel) settingsViewModel.onDeleteClicked(fingerprintViewModel)
} }
override fun showSettings(state: FingerprintStateViewModel) { override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) {
val category = val category =
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY) this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
as PreferenceCategory? as PreferenceCategory?
category?.removeAll() category?.removeAll()
state.fingerprintViewModels.forEach { fingerprint -> enrolledFingerprints.forEach { fingerprint ->
category?.addPreference( category?.addPreference(
FingerprintSettingsPreference( FingerprintSettingsPreference(
requireContext(), requireContext(),
fingerprint, fingerprint,
this@FingerprintSettingsV2Fragment, this@FingerprintSettingsV2Fragment,
state.fingerprintViewModels.size == 1, enrolledFingerprints.size == 1,
) )
) )
} }
category?.isVisible = true category?.isVisible = true
createFingerprintsFooterPreference(state.canEnroll, state.maxFingerprints)
preferenceScreen.isVisible = true 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 = val sideFpsPref =
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_CATEGORY) this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_CATEGORY)
as PreferenceCategory? as PreferenceCategory?
sideFpsPref?.isVisible = false sideFpsPref?.isVisible = isSfpsPrefVisible
if (state.hasSideFps) {
sideFpsPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
val otherPref = val otherPref =
this@FingerprintSettingsV2Fragment.findPreference( this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH)
KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH as Preference?
) as Preference? otherPref?.isVisible = isSfpsPrefVisible
otherPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
} }
addFooter(state.hasSideFps)
} private fun addFooter() {
private fun addFooter(hasSideFps: Boolean) {
val footer = val footer =
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_FOOTER) this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_FOOTER)
as PreferenceCategory? as PreferenceCategory?
@@ -380,10 +387,8 @@ class FingerprintSettingsV2Fragment :
footerColumns.add(column1) footerColumns.add(column1)
val column2 = FooterColumn() val column2 = FooterColumn()
column2.title = getText(R.string.security_fingerprint_disclaimer_lockscreen_disabled_2) column2.title = getText(R.string.security_fingerprint_disclaimer_lockscreen_disabled_2)
if (hasSideFps) {
column2.learnMoreOverrideText = column2.learnMoreOverrideText =
getText(R.string.security_settings_fingerprint_settings_footer_learn_more) getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
}
column2.learnMoreOnClickListener = learnMoreClickListener column2.learnMoreOnClickListener = learnMoreClickListener
footerColumns.add(column2) footerColumns.add(column2)
} else { } else {
@@ -394,10 +399,8 @@ class FingerprintSettingsV2Fragment :
DeviceHelper.getDeviceName(requireActivity()) DeviceHelper.getDeviceName(requireActivity())
) )
column.learnMoreOnClickListener = learnMoreClickListener column.learnMoreOnClickListener = learnMoreClickListener
if (hasSideFps) {
column.learnMoreOverrideText = column.learnMoreOverrideText =
getText(R.string.security_settings_fingerprint_settings_footer_learn_more) getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
}
footerColumns.add(column) footerColumns.add(column)
} }
@@ -550,18 +553,6 @@ class FingerprintSettingsV2Fragment :
} }
} }
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?> { private fun fingerprintPreferences(): List<FingerprintSettingsPreference?> {
val category = val category =
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY) this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)

View File

@@ -18,26 +18,27 @@ package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel
import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor 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.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.shared.model.FingerprintViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -52,13 +53,30 @@ class FingerprintSettingsViewModel(
private val backgroundDispatcher: CoroutineDispatcher, private val backgroundDispatcher: CoroutineDispatcher,
private val navigationViewModel: FingerprintSettingsNavigationViewModel, private val navigationViewModel: FingerprintSettingsNavigationViewModel,
) : ViewModel() { ) : ViewModel() {
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> =
private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
private val fingerprintSensorPropertiesInternal:
MutableStateFlow<List<FingerprintSensorPropertiesInternal>?> =
MutableStateFlow(null) 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) private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
val isShowingDialog = val isShowingDialog =
_isShowingDialog.combine(navigationViewModel.nextStep) { dialogFlow, nextStep -> _isShowingDialog.combine(navigationViewModel.nextStep) { dialogFlow, nextStep ->
@@ -69,16 +87,13 @@ class FingerprintSettingsViewModel(
} }
} }
private val _fingerprintStateViewModel: MutableStateFlow<FingerprintStateViewModel?> = private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
MutableStateFlow(null)
val fingerprintState: Flow<FingerprintStateViewModel?> = private val _fingerprintSensorType: Flow<Int> =
_fingerprintStateViewModel.combineTransform(navigationViewModel.nextStep) { fingerprintManagerInteractor.sensorPropertiesInternal.transform { it?.sensorType }
settingsShowingViewModel,
currStep -> private val _sensorNullOrEmpty: Flow<Boolean> =
if (currStep != null && currStep is ShowSettings) { fingerprintManagerInteractor.sensorPropertiesInternal.map{it ==null}
emit(settingsShowingViewModel)
}
}
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> = private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
MutableStateFlow(null) MutableStateFlow(null)
@@ -86,7 +101,7 @@ class FingerprintSettingsViewModel(
private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> = private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
MutableSharedFlow() 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 * 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 * implementation would take quite a lot of code to implement, it might be easier to rewrite
@@ -101,11 +116,20 @@ class FingerprintSettingsViewModel(
_isShowingDialog, _isShowingDialog,
navigationViewModel.nextStep, navigationViewModel.nextStep,
_consumerShouldAuthenticate, _consumerShouldAuthenticate,
_fingerprintStateViewModel, _enrolledFingerprints,
_isLockedOut, _isLockedOut,
attemptsSoFar, _attemptsSoFar,
fingerprintSensorPropertiesInternal _fingerprintSensorType,
) { dialogShowing, step, resume, fingerprints, isLockedOut, attempts, sensorProps -> _sensorNullOrEmpty
) {
dialogShowing,
step,
resume,
fingerprints,
isLockedOut,
attempts,
sensorType,
sensorNullOrEmpty ->
if (DEBUG) { if (DEBUG) {
Log.d( Log.d(
TAG, TAG,
@@ -115,13 +139,13 @@ class FingerprintSettingsViewModel(
"fingerprints=${fingerprints}," + "fingerprints=${fingerprints}," +
"lockedOut=${isLockedOut}," + "lockedOut=${isLockedOut}," +
"attempts=${attempts}," + "attempts=${attempts}," +
"sensorProps=${sensorProps}" "sensorType=${sensorType}" +
"sensorNullOrEmpty=${sensorNullOrEmpty}"
) )
} }
if (sensorProps.isNullOrEmpty()) { if (sensorNullOrEmpty) {
return@combine false return@combine false
} }
val sensorType = sensorProps[0].sensorType
if ( if (
listOf( listOf(
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
@@ -133,7 +157,7 @@ class FingerprintSettingsViewModel(
} }
if (step != null && step is ShowSettings) { if (step != null && step is ShowSettings) {
if (fingerprints?.fingerprintViewModels?.isNotEmpty() == true) { if (fingerprints?.isNotEmpty() == true) {
return@combine dialogShowing == null && isLockedOut == null && resume && attempts < 15 return@combine dialogShowing == null && isLockedOut == null && resume && attempts < 15
} }
} }
@@ -172,18 +196,12 @@ class FingerprintSettingsViewModel(
.flowOn(backgroundDispatcher) .flowOn(backgroundDispatcher)
init { init {
viewModelScope.launch {
fingerprintSensorPropertiesInternal.update {
fingerprintManagerInteractor.sensorPropertiesInternal()
}
}
viewModelScope.launch { viewModelScope.launch {
navigationViewModel.nextStep.filterNotNull().collect { navigationViewModel.nextStep.filterNotNull().collect {
_isShowingDialog.update { null } _isShowingDialog.update { null }
if (it is ShowSettings) { if (it is ShowSettings) {
// reset state // reset state
updateSettingsData() updateEnrolledFingerprints()
} }
} }
} }
@@ -200,7 +218,7 @@ class FingerprintSettingsViewModel(
} }
override fun toString(): String { 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. */ /** The fingerprint delete button has been clicked. */
@@ -229,7 +247,7 @@ class FingerprintSettingsViewModel(
fun deleteFingerprint(fp: FingerprintViewModel) { fun deleteFingerprint(fp: FingerprintViewModel) {
viewModelScope.launch(backgroundDispatcher) { viewModelScope.launch(backgroundDispatcher) {
if (fingerprintManagerInteractor.removeFingerprint(fp)) { if (fingerprintManagerInteractor.removeFingerprint(fp)) {
updateSettingsData() updateEnrolledFingerprints()
} }
} }
} }
@@ -238,45 +256,25 @@ class FingerprintSettingsViewModel(
fun renameFingerprint(fp: FingerprintViewModel, newName: String) { fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
viewModelScope.launch { viewModelScope.launch {
fingerprintManagerInteractor.renameFingerprint(fp, newName) fingerprintManagerInteractor.renameFingerprint(fp, newName)
updateSettingsData() updateEnrolledFingerprints()
} }
} }
private fun attemptingAuth() { private fun attemptingAuth() {
attemptsSoFar.update { it + 1 } _attemptsSoFar.update { it + 1 }
} }
private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) { private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
_authSucceeded.emit(success) _authSucceeded.emit(success)
attemptsSoFar.update { 0 } _attemptsSoFar.update { 0 }
} }
private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) { private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
_isLockedOut.update { attemptViewModel } _isLockedOut.update { attemptViewModel }
} }
/** private suspend fun updateEnrolledFingerprints() {
* This function is sort of a hack, it's used whenever we want to check for fingerprint state _enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() }
* 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(),
)
}
} }
/** Used to indicate whether the consumer of the view model is ready for authentication. */ /** Used to indicate whether the consumer of the view model is ready for authentication. */
@@ -284,6 +282,13 @@ class FingerprintSettingsViewModel(
_consumerShouldAuthenticate.update { authenticate } _consumerShouldAuthenticate.update { authenticate }
} }
private fun <T> Flow<T>.filterOnlyWhenSettingsIsShown() =
combineTransform(navigationViewModel.nextStep) { value, currStep ->
if (currStep != null && currStep is ShowSettings) {
emit(value)
}
}
class FingerprintSettingsViewModelFactory( class FingerprintSettingsViewModelFactory(
private val userId: Int, private val userId: Int,
private val interactor: FingerprintManagerInteractor, private val interactor: FingerprintManagerInteractor,
@@ -307,7 +312,7 @@ class FingerprintSettingsViewModel(
} }
} }
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>, flow: Flow<T1>,
flow2: Flow<T2>, flow2: Flow<T2>,
flow3: Flow<T3>, flow3: Flow<T3>,
@@ -315,9 +320,10 @@ private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow5: Flow<T5>, flow5: Flow<T5>,
flow6: Flow<T6>, flow6: Flow<T6>,
flow7: Flow<T7>, 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> { ): 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") @Suppress("UNCHECKED_CAST")
transform( transform(
args[0] as T1, args[0] as T1,
@@ -327,6 +333,7 @@ private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
args[4] as T5, args[4] as T5,
args[5] as T6, args[5] as T6,
args[6] as T7, args[6] as T7,
args[7] as T8,
) )
} }
} }

View File

@@ -23,7 +23,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import kotlinx.coroutines.flow.Flow 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. */ /** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor { class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
@@ -53,15 +53,16 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> { override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
return challengeToGenerate return challengeToGenerate
} }
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow { override val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
emit(enrolledFingerprintsInternal) flowOf(enrolledFingerprintsInternal)
}
override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow { override val canEnrollFingerprints: Flow<Boolean> =
emit(numFingerprints < enrollableFingerprints) 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 { override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
return enrolledFingerprintsInternal.remove(fp) return enrolledFingerprintsInternal.remove(fp)
@@ -80,7 +81,4 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
override suspend fun pressToAuthEnabled(): Boolean { override suspend fun pressToAuthEnabled(): Boolean {
return pressToAuthEnabled return pressToAuthEnabled
} }
override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
sensorProps
} }

View File

@@ -18,7 +18,6 @@ package com.android.settings.fingerprint2.domain.interactor
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Resources
import android.hardware.fingerprint.Fingerprint import android.hardware.fingerprint.Fingerprint
import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager.CryptoObject import android.hardware.fingerprint.FingerprintManager.CryptoObject
@@ -51,8 +50,11 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.nullable import org.mockito.ArgumentMatchers.nullable
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnitRunner import org.mockito.junit.MockitoJUnitRunner
import org.mockito.stubbing.OngoingStubbing
@RunWith(MockitoJUnitRunner::class) @RunWith(MockitoJUnitRunner::class)
class FingerprintManagerInteractorTest { class FingerprintManagerInteractorTest {
@@ -82,8 +84,7 @@ class FingerprintManagerInteractorTest {
@Test @Test
fun testEmptyFingerprints() = fun testEmptyFingerprints() =
testScope.runTest { testScope.runTest {
Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt())) whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
.thenReturn(emptyList())
val emptyFingerprintList: List<Fingerprint> = emptyList() val emptyFingerprintList: List<Fingerprint> = emptyList()
assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList) assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
@@ -94,8 +95,7 @@ class FingerprintManagerInteractorTest {
testScope.runTest { testScope.runTest {
val expected = Fingerprint("Finger 1,", 2, 3L) val expected = Fingerprint("Finger 1,", 2, 3L)
val fingerprintList: List<Fingerprint> = listOf(expected) val fingerprintList: List<Fingerprint> = listOf(expected)
Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt())) whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
.thenReturn(fingerprintList)
val list = underTest.enrolledFingerprints.last() val list = underTest.enrolledFingerprints.last()
assertThat(list.size).isEqualTo(fingerprintList.size) assertThat(list.size).isEqualTo(fingerprintList.size)
@@ -108,21 +108,22 @@ class FingerprintManagerInteractorTest {
@Test @Test
fun testCanEnrollFingerprint() = fun testCanEnrollFingerprint() =
testScope.runTest { testScope.runTest {
val mockContext = Mockito.mock(Context::class.java) val fingerprintList1: List<Fingerprint> =
val resources = Mockito.mock(Resources::class.java) listOf(
Mockito.`when`(mockContext.resources).thenReturn(resources) Fingerprint("Finger 1,", 2, 3L),
Mockito.`when`(resources.getInteger(anyInt())).thenReturn(3) Fingerprint("Finger 2,", 3, 3L),
underTest = Fingerprint("Finger 3,", 4, 3L)
FingerprintManagerInteractorImpl(
mockContext,
backgroundDispatcher,
fingerprintManager,
gateKeeperPasswordProvider,
pressToAuthProvider,
) )
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.last()).isTrue()
assertThat(underTest.canEnrollFingerprints(3).last()).isFalse() assertThat(underTest.canEnrollFingerprints.last()).isFalse()
} }
@Test @Test
@@ -132,7 +133,7 @@ class FingerprintManagerInteractorTest {
val challenge = 100L val challenge = 100L
val intent = Intent() val intent = Intent()
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge) intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge)
Mockito.`when`( whenever(
gateKeeperPasswordProvider.requestGatekeeperHat( gateKeeperPasswordProvider.requestGatekeeperHat(
any(Intent::class.java), any(Intent::class.java),
anyLong(), anyLong(),
@@ -148,8 +149,7 @@ class FingerprintManagerInteractorTest {
val job = testScope.launch { result = underTest.generateChallenge(1L) } val job = testScope.launch { result = underTest.generateChallenge(1L) }
runCurrent() runCurrent()
Mockito.verify(fingerprintManager) verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback))
.generateChallenge(anyInt(), capture(generateChallengeCallback))
generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge) generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge)
runCurrent() runCurrent()
@@ -173,7 +173,7 @@ class FingerprintManagerInteractorTest {
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) } testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
runCurrent() runCurrent()
Mockito.verify(fingerprintManager) verify(fingerprintManager)
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback)) .remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1) removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1)
@@ -197,7 +197,7 @@ class FingerprintManagerInteractorTest {
testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) } testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
runCurrent() runCurrent()
Mockito.verify(fingerprintManager) verify(fingerprintManager)
.remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback)) .remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
removalCallback.value.onRemovalError( removalCallback.value.onRemovalError(
fingerprintToRemove, fingerprintToRemove,
@@ -218,8 +218,7 @@ class FingerprintManagerInteractorTest {
underTest.renameFingerprint(fingerprintToRename, "Woo") underTest.renameFingerprint(fingerprintToRename, "Woo")
Mockito.verify(fingerprintManager) verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
.rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
} }
@Test @Test
@@ -235,7 +234,7 @@ class FingerprintManagerInteractorTest {
runCurrent() runCurrent()
Mockito.verify(fingerprintManager) verify(fingerprintManager)
.authenticate( .authenticate(
nullable(CryptoObject::class.java), nullable(CryptoObject::class.java),
any(CancellationSignal::class.java), any(CancellationSignal::class.java),
@@ -263,7 +262,7 @@ class FingerprintManagerInteractorTest {
runCurrent() runCurrent()
Mockito.verify(fingerprintManager) verify(fingerprintManager)
.authenticate( .authenticate(
nullable(CryptoObject::class.java), nullable(CryptoObject::class.java),
any(CancellationSignal::class.java), any(CancellationSignal::class.java),
@@ -284,4 +283,5 @@ class FingerprintManagerInteractorTest {
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() 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> any(type: Class<T>): T = Mockito.any<T>(type)
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
} }