Merge "Adding more tests for FingerprintSettingsV2" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
12bf914caf
@@ -26,6 +26,7 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||
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 @@ class FingerprintSettingsNavigationViewModel(
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ class FingerprintSettingsViewModel(
|
||||
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 @@ class FingerprintSettingsViewModel(
|
||||
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 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
.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 }
|
||||
|
@@ -67,7 +67,11 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
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 }
|
||||
|
@@ -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 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@@ -213,7 +213,8 @@ class FingerprintSettingsViewModelTest {
|
||||
@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 @@ class FingerprintSettingsViewModelTest {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user