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