Adding more tests for FingerprintSettingsV2
Test: atest FingerprintSettingsViewModelTest
FingerprintSettingsNavigationModelTest
Bug: 280862076
Change-Id: Ibb3d0112f394d6776fc1b346d226d9f7720cfed8
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt
index a3a5d3c..a638806 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt
@@ -26,6 +26,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -49,7 +50,13 @@
if (challengeInit == null || tokenInit == null) {
_nextStep.update { LaunchConfirmDeviceCredential(userId) }
} else {
- viewModelScope.launch { showSettingsHelper() }
+ viewModelScope.launch {
+ if (fingerprintManagerInteractor.enrolledFingerprints.last().isEmpty()) {
+ _nextStep.update { EnrollFirstFingerprint(userId, null, challenge, token) }
+ } else {
+ showSettingsHelper()
+ }
+ }
}
}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
index 554f336..0bae075 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
@@ -17,8 +17,7 @@
package com.android.settings.biometrics.fingerprint2.ui.viewmodel
import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
-import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.util.Log
import androidx.lifecycle.ViewModel
@@ -67,24 +66,6 @@
}
}
- init {
- viewModelScope.launch {
- fingerprintSensorPropertiesInternal.update {
- fingerprintManagerInteractor.sensorPropertiesInternal()
- }
- }
-
- viewModelScope.launch {
- navigationViewModel.nextStep.filterNotNull().collect {
- _isShowingDialog.update { null }
- if (it is ShowSettings) {
- // reset state
- updateSettingsData()
- }
- }
- }
- }
-
private val _fingerprintStateViewModel: MutableStateFlow<FingerprintStateViewModel?> =
MutableStateFlow(null)
val fingerprintState: Flow<FingerprintStateViewModel?> =
@@ -103,7 +84,6 @@
MutableSharedFlow()
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
@@ -139,7 +119,13 @@
return@combine false
}
val sensorType = sensorProps[0].sensorType
- if (listOf(TYPE_UDFPS_OPTICAL, TYPE_UDFPS_ULTRASONIC).contains(sensorType)) {
+ if (
+ listOf(
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC
+ )
+ .contains(sensorType)
+ ) {
return@combine false
}
@@ -182,6 +168,24 @@
}
.flowOn(backgroundDispatcher)
+ init {
+ viewModelScope.launch {
+ fingerprintSensorPropertiesInternal.update {
+ fingerprintManagerInteractor.sensorPropertiesInternal()
+ }
+ }
+
+ viewModelScope.launch {
+ navigationViewModel.nextStep.filterNotNull().collect {
+ _isShowingDialog.update { null }
+ if (it is ShowSettings) {
+ // reset state
+ updateSettingsData()
+ }
+ }
+ }
+ }
+
/** The rename dialog has finished */
fun onRenameDialogFinished() {
_isShowingDialog.update { null }
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 0509d8a..1848c01 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
@@ -67,7 +67,11 @@
return enrolledFingerprintsInternal.remove(fp)
}
- override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {}
+ override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+ if (enrolledFingerprintsInternal.remove(fp)) {
+ enrolledFingerprintsInternal.add(FingerprintViewModel(newName, fp.fingerId, fp.deviceId))
+ }
+ }
override suspend fun hasSideFps(): Boolean {
return sensorProps.any { it.isAnySidefpsType }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt
index 4e1f6b1..9206afb 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.settings.fingerprint2.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
@@ -272,4 +273,97 @@
assertThat(nextStep).isEqualTo(ShowSettings)
job.cancel()
}
+
+ @Test
+ fun enrollWithToken_andNoUsers_startsFingerprintEnrollment() =
+ testScope.runTest {
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+ var nextStep: NextStepViewModel? = null
+ val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+ val token = byteArrayOf(1)
+ val challenge = 5L
+
+ underTest =
+ FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
+ defaultUserId,
+ fakeFingerprintManagerInteractor,
+ backgroundDispatcher,
+ token,
+ challenge,
+ )
+ .create(FingerprintSettingsNavigationViewModel::class.java)
+
+ runCurrent()
+
+ assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, null, challenge, token))
+ job.cancel()
+ }
+
+ @Test
+ fun enroll_shouldNotFinish() =
+ testScope.runTest {
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+ var nextStep: NextStepViewModel? = null
+ val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+ val token = byteArrayOf(1)
+ val challenge = 5L
+
+ underTest =
+ FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
+ defaultUserId,
+ fakeFingerprintManagerInteractor,
+ backgroundDispatcher,
+ token,
+ challenge,
+ )
+ .create(FingerprintSettingsNavigationViewModel::class.java)
+
+ runCurrent()
+
+ assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, null, challenge, token))
+ underTest.maybeFinishActivity(false)
+
+ runCurrent()
+ assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, null, challenge, token))
+ job.cancel()
+ }
+
+ @Test
+ fun showSettings_shouldFinish() =
+ testScope.runTest {
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+ mutableListOf(FingerprintViewModel("a", 1, 3L))
+
+ var nextStep: NextStepViewModel? = null
+ val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+ val token = byteArrayOf(1)
+ val challenge = 5L
+
+ underTest =
+ FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
+ defaultUserId,
+ fakeFingerprintManagerInteractor,
+ backgroundDispatcher,
+ token,
+ challenge,
+ )
+ .create(FingerprintSettingsNavigationViewModel::class.java)
+
+ runCurrent()
+ assertThat(nextStep).isEqualTo(ShowSettings)
+
+ underTest.maybeFinishActivity(false)
+
+ runCurrent()
+ assertThat(nextStep)
+ .isEqualTo(
+ FinishSettingsWithResult(BiometricEnrollBase.RESULT_TIMEOUT, "onStop finishing settings")
+ )
+ job.cancel()
+ }
}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
index d430827..8bd0b10 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
@@ -213,7 +213,8 @@
@Test
fun deleteDialog_showAndDismiss() = runTest {
val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
- fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(fingerprintToDelete)
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+ mutableListOf(fingerprintToDelete)
underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -244,4 +245,170 @@
dialogJob.cancel()
}
+
+ @Test
+ fun renameDialog_showAndDismiss() = runTest {
+ val fingerprintToRename = FingerprintViewModel("World", 1, 10L)
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+ mutableListOf(fingerprintToRename)
+
+ underTest =
+ FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+ defaultUserId,
+ fakeFingerprintManagerInteractor,
+ backgroundDispatcher,
+ navigationViewModel,
+ )
+ .create(FingerprintSettingsViewModel::class.java)
+
+ var dialog: PreferenceViewModel? = null
+ val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
+
+ // Move to the ShowSettings state
+ navigationViewModel.onConfirmDevice(true, 10L)
+ runCurrent()
+ underTest.onPrefClicked(fingerprintToRename)
+ runCurrent()
+
+ assertThat(dialog is PreferenceViewModel.DeleteDialog)
+ assertThat(dialog).isEqualTo(PreferenceViewModel.RenameDialog(fingerprintToRename))
+
+ underTest.renameFingerprint(fingerprintToRename, "Hello")
+ underTest.onRenameDialogFinished()
+ runCurrent()
+
+ assertThat(dialog).isNull()
+ assertThat(fakeFingerprintManagerInteractor.enrolledFingerprintsInternal.first().name)
+ .isEqualTo("Hello")
+
+ dialogJob.cancel()
+ }
+
+ @Test
+ fun testTwoDialogsCannotShow_atSameTime() = runTest {
+ val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+ mutableListOf(fingerprintToDelete)
+
+ underTest =
+ FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+ defaultUserId,
+ fakeFingerprintManagerInteractor,
+ backgroundDispatcher,
+ navigationViewModel,
+ )
+ .create(FingerprintSettingsViewModel::class.java)
+
+ var dialog: PreferenceViewModel? = null
+ val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
+
+ // Move to the ShowSettings state
+ navigationViewModel.onConfirmDevice(true, 10L)
+ runCurrent()
+ underTest.onDeleteClicked(fingerprintToDelete)
+ runCurrent()
+
+ assertThat(dialog is PreferenceViewModel.DeleteDialog)
+ assertThat(dialog).isEqualTo(PreferenceViewModel.DeleteDialog(fingerprintToDelete))
+
+ underTest.onPrefClicked(fingerprintToDelete)
+ runCurrent()
+ assertThat(dialog is PreferenceViewModel.DeleteDialog)
+ assertThat(dialog).isEqualTo(PreferenceViewModel.DeleteDialog(fingerprintToDelete))
+
+ dialogJob.cancel()
+ }
+
+ @Test
+ fun authenticatePauses_whenPaused() =
+ testScope.runTest {
+ val fingerprints = setupAuth()
+ val success = FingerprintAuthAttemptViewModel.Success(fingerprints.first().fingerId)
+
+ var authAttempt: FingerprintAuthAttemptViewModel? = null
+
+ val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
+
+ underTest.shouldAuthenticate(true)
+ navigationViewModel.onConfirmDevice(true, 10L)
+
+ advanceTimeBy(400)
+ runCurrent()
+ assertThat(authAttempt).isEqualTo(success)
+
+ fakeFingerprintManagerInteractor.authenticateAttempt =
+ FingerprintAuthAttemptViewModel.Success(10)
+ underTest.shouldAuthenticate(false)
+ advanceTimeBy(400)
+ runCurrent()
+
+ // The most recent auth attempt shouldn't have changed.
+ assertThat(authAttempt).isEqualTo(success)
+ job.cancel()
+ }
+
+ @Test
+ fun dialog_pausesAuth() =
+ testScope.runTest {
+ val fingerprints = setupAuth()
+
+ var authAttempt: FingerprintAuthAttemptViewModel? = null
+ val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
+ underTest.shouldAuthenticate(true)
+ navigationViewModel.onConfirmDevice(true, 10L)
+
+ underTest.onPrefClicked(fingerprints[0])
+ advanceTimeBy(400)
+
+ job.cancel()
+ assertThat(authAttempt).isEqualTo(null)
+ }
+
+ @Test
+ fun cannotAuth_when_notShowingSettings() =
+ testScope.runTest {
+ val fingerprints = setupAuth()
+
+ var authAttempt: FingerprintAuthAttemptViewModel? = null
+ val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
+ underTest.shouldAuthenticate(true)
+ navigationViewModel.onConfirmDevice(true, 10L)
+
+ // This should cause the state to change to FingerprintEnrolling
+ navigationViewModel.onAddFingerprintClicked()
+ advanceTimeBy(400)
+
+ job.cancel()
+ assertThat(authAttempt).isEqualTo(null)
+ }
+
+ private fun setupAuth(): MutableList<FingerprintViewModel> {
+ fakeFingerprintManagerInteractor.sensorProps =
+ listOf(
+ FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ emptyList() /* ComponentInfoInternal */,
+ FingerprintSensorProperties.TYPE_POWER_BUTTON,
+ true /* resetLockoutRequiresHardwareAuthToken */
+ )
+ )
+ val fingerprints =
+ mutableListOf(FingerprintViewModel("a", 1, 3L), FingerprintViewModel("b", 2, 5L))
+ fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fingerprints
+ val success = FingerprintAuthAttemptViewModel.Success(1)
+ fakeFingerprintManagerInteractor.authenticateAttempt = success
+
+ underTest =
+ FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+ defaultUserId,
+ fakeFingerprintManagerInteractor,
+ backgroundDispatcher,
+ navigationViewModel,
+ )
+ .create(FingerprintSettingsViewModel::class.java)
+
+ return fingerprints
+ }
}