Moving FingerprintSettings to Kotlin

This change is the first of many, it will

1. Change java -> kotlin
2. Use the MVVM architecture
3. Be feature flagged

This change in particular is focused on transitions to and from various activities.

Enable feature via
adb shell setprop sys.fflag.override.settings_biometrics2_fingerprint true

Bug: 280862076
Test: atest FingerprintSettingsViewModelTest
Change-Id: I8eb5c30e6f2e92c256ae7c257a9d560439ba418f
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 8dfb52e..cb57bc7 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -32,6 +32,7 @@
         "platform-test-annotations",
         "truth-prebuilt",
         "androidx.test.uiautomator_uiautomator",
+        "kotlinx_coroutines_test",
         // Don't add SettingsLib libraries here - you can use them directly as they are in the
         // instrumented Settings app.
     ],
@@ -39,8 +40,7 @@
     errorprone: {
         javacflags: ["-Xep:CheckReturnValue:WARN"]
     },
-
-    // Include all test java files.
+    // Include all test java/kotlin files.
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
diff --git a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
new file mode 100644
index 0000000..7389543
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
@@ -0,0 +1,332 @@
+/*
+ * 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.fingerprint2.viewmodel
+
+import android.hardware.fingerprint.Fingerprint
+import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.NextStepViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(MockitoJUnitRunner::class)
+class FingerprintSettingsViewModelTest {
+
+    @JvmField
+    @Rule
+    var rule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var fingerprintManager: FingerprintManager
+    private lateinit var underTest: FingerprintSettingsViewModel
+    private val defaultUserId = 0
+
+    @Before
+    fun setup() {
+        // @formatter:off
+        underTest = FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+            defaultUserId,
+            fingerprintManager,
+        ).create(FingerprintSettingsViewModel::class.java)
+        // @formatter:on
+    }
+
+    @Test
+    fun testNoGateKeeper_launchesConfirmDeviceCredential() = runTest {
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+
+        runCurrent()
+        assertThat(nextStep).isEqualTo(LaunchConfirmDeviceCredential(defaultUserId))
+        job.cancel()
+    }
+
+    @Test
+    fun testConfirmDevice_fails() = runTest {
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(false, null)
+
+        runCurrent()
+
+        assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+        job.cancel()
+    }
+
+    @Test
+    fun confirmDeviceSuccess_noGateKeeper() = runTest {
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, null)
+
+        runCurrent()
+
+        assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+        job.cancel()
+    }
+
+    @Test
+    fun confirmDeviceSuccess_launchesEnrollment_ifNoPreviousEnrollments() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, 10L, null, null))
+        job.cancel()
+    }
+
+    @Test
+    fun firstEnrollment_fails() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollFirstFailure("We failed!!")
+
+        runCurrent()
+
+        assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+        job.cancel()
+    }
+
+    @Test
+    fun firstEnrollment_failsWithReason() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        val failStr = "We failed!!"
+        val failReason = 101
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollFirstFailure(failStr, failReason)
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(FinishSettingsWithResult(failReason, failStr))
+        job.cancel()
+    }
+
+    @Test
+    fun firstEnrollmentSucceeds_noToken() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollFirst(null, null)
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(FinishSettings("Error, empty token"))
+        job.cancel()
+    }
+
+    @Test
+    fun firstEnrollmentSucceeds_noKeyChallenge() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        val byteArray = ByteArray(1) {
+            3
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollFirst(byteArray, null)
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(FinishSettings("Error, empty keyChallenge"))
+        job.cancel()
+    }
+
+    @Test
+    fun firstEnrollment_succeeds() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        val byteArray = ByteArray(1) {
+            3
+        }
+        val keyChallenge = 89L
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollFirst(byteArray, keyChallenge)
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
+        job.cancel()
+    }
+
+    @Test
+    fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
+            listOf(
+                Fingerprint(
+                    "a", 1, 2, 3L
+                )
+            )
+        )
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
+        job.cancel()
+    }
+
+    @Test
+    fun enrollAdditionalFingerprints_fails() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
+            listOf(
+                Fingerprint(
+                    "a", 1, 2, 3L
+                )
+            )
+        )
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollAdditionalFailure()
+
+        runCurrent()
+
+        assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+        job.cancel()
+    }
+
+    @Test
+    fun enrollAdditional_success() = runTest {
+        whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
+            listOf(
+                Fingerprint(
+                    "a", 1, 2, 3L
+                )
+            )
+        )
+
+        var nextStep: NextStepViewModel? = null
+        val job = launch {
+            underTest.nextStep.collect {
+                nextStep = it
+            }
+        }
+
+        underTest.updateTokenAndChallenge(null, null)
+        underTest.onConfirmDevice(true, 10L)
+        underTest.onEnrollSuccess()
+
+        runCurrent()
+
+        assertThat(nextStep).isEqualTo(ShowSettings(defaultUserId))
+        job.cancel()
+    }
+}
\ No newline at end of file