Add enroll() in FingerprintManagerInteractor.

Test: atest FingerprintManagerInteractorTest
Bug: 295206773

Change-Id: If2fc46b1c952c3e55c698a18e125e194efe5ffb6
This commit is contained in:
Hao Dong
2023-08-28 17:41:45 +00:00
parent 26cd19552b
commit 2200f55ffe
13 changed files with 307 additions and 37 deletions

View File

@@ -27,12 +27,18 @@ import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.toOriginalReason
import com.android.settings.password.ChooseLockSettingsHelper
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -66,6 +72,16 @@ interface FingerprintManagerInteractor {
*/
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
/**
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
* enrollment. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
* enrollment state.
*/
suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
): Flow<FingerEnrollStateViewModel>
/**
* Removes the given fingerprint, returning true if it was successfully removed and false
* otherwise
@@ -133,6 +149,51 @@ class FingerprintManagerInteractorImpl(
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason,
): Flow<FingerEnrollStateViewModel> = callbackFlow {
var streamEnded = false
val enrollmentCallback =
object : FingerprintManager.EnrollmentCallback() {
override fun onEnrollmentProgress(remaining: Int) {
trySend(FingerEnrollStateViewModel.EnrollProgress(remaining)).onFailure { error ->
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
}
if (remaining == 0) {
streamEnded = true
}
}
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
trySend(FingerEnrollStateViewModel.EnrollHelp(helpMsgId, helpString.toString()))
.onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
}
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
trySend(FingerEnrollStateViewModel.EnrollError(errMsgId, errString.toString()))
.onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
streamEnded = true
}
}
val cancellationSignal = CancellationSignal()
fingerprintManager.enroll(
hardwareAuthToken,
cancellationSignal,
applicationContext.userId,
enrollmentCallback,
enrollReason.toOriginalReason()
)
awaitClose {
// If the stream has not been ended, and the user has stopped collecting the flow
// before it was over, send cancel.
if (!streamEnded) {
cancellationSignal.cancel()
}
}
}
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
val callback =
object : RemovalCallback() {

View File

@@ -43,12 +43,12 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollmentIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
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.Enrollment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
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.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
@@ -70,7 +70,7 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
* children fragments.
*/
class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
private val coroutineDispatcher = Dispatchers.Default
@@ -170,18 +170,18 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
navigationViewModel =
ViewModelProvider(
this,
FingerprintEnrollmentNavigationViewModel.FingerprintEnrollmentNavigationViewModelFactory(
FingerprintEnrollNavigationViewModel.FingerprintEnrollNavigationViewModelFactory(
backgroundDispatcher,
interactor,
gatekeeperViewModel,
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
)
)[FingerprintEnrollmentNavigationViewModel::class.java]
)[FingerprintEnrollNavigationViewModel::class.java]
// Initialize FingerprintViewModel
ViewModelProvider(
this,
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor)
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor, backgroundDispatcher)
)[FingerprintEnrollViewModel::class.java]
// Initialize scroll view model
@@ -198,7 +198,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
Intro -> FingerprintEnrollmentIntroV2Fragment::class.java as Class<Fragment>
Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
else -> null
}

View File

@@ -19,7 +19,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/**
* A fragment to indicate that fingerprint enrollment has been completed.
@@ -33,7 +33,7 @@ class FingerprintEnrollConfirmationV2Fragment : Fragment() {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val navigationViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
}
}
}

View File

@@ -19,7 +19,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/** A fragment that is responsible for enrolling a users fingerprint. */
class FingerprintEnrollEnrollingV2Fragment : Fragment() {
@@ -28,7 +28,7 @@ class FingerprintEnrollEnrollingV2Fragment : Fragment() {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val navigationViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
}
}
}

View File

@@ -20,7 +20,7 @@ import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollmentNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
/**
* A fragment that is used to educate the user about the fingerprint sensor on this device.
@@ -36,7 +36,7 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment(R.layout.fingerprint_v2_e
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val navigationViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
}
}
}

View File

@@ -33,8 +33,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
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.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
@@ -72,10 +72,10 @@ private data class TextModel(
* 2. How the data will be stored
* 3. How the user can access and remove their data
*/
class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_introduction) {
private lateinit var footerBarMixin: FooterBarMixin
private lateinit var textModel: TextModel
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
@@ -83,7 +83,7 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navigationViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
fingerprintEnrollViewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
fingerprintScrollViewModel =

View File

@@ -0,0 +1,36 @@
/*
* 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.FingerprintManager
/**
* The reason for enrollment. Represents [FingerprintManager.EnrollReason]
*/
enum class EnrollReason {
/** The enroll happens on education screen. */
FindSensor,
/** The enroll happens on enrolling screen. */
EnrollEnrolling
}
/** Convert EnrollReason to original [FingerprintManager.EnrollReason]. */
fun EnrollReason.toOriginalReason(): Int {
return when (this) {
EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.annotation.StringRes
/**
* Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
* information
*/
sealed class FingerEnrollStateViewModel {
/** Represents enrollment step progress. */
data class EnrollProgress(
val remainingSteps: Int,
) : FingerEnrollStateViewModel()
/** Represents that recoverable error has been encountered during enrollment. */
data class EnrollHelp(
@StringRes val helpMsgId: Int,
val helpString: String,
) : FingerEnrollStateViewModel()
/** Represents that an unrecoverable error has been encountered and the operation is complete. */
data class EnrollError(
@StringRes val errMsgId: Int,
val errString: String,
) : FingerEnrollStateViewModel()
}

View File

@@ -13,7 +13,6 @@
* 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
@@ -21,13 +20,24 @@ import androidx.lifecycle.ViewModelProvider
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.toSensorType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update
/** Represents all of the fingerprint information needed for fingerprint enrollment. */
class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
ViewModel() {
private const val TAG = "FingerprintEnrollViewModel"
/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
class FingerprintEnrollViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
backgroundDispatcher: CoroutineDispatcher,
) : ViewModel() {
/** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<FingerprintSensorType> =
@@ -35,14 +45,55 @@ class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManage
it.sensorType.toSensorType()
}
class FingerprintEnrollViewModelFactory(val interactor: FingerprintManagerInteractor) :
ViewModelProvider.Factory {
private var _enrollReason: MutableStateFlow<EnrollReason> =
MutableStateFlow(EnrollReason.FindSensor)
private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null)
private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false)
/**
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
* an enrollment process
*/
val enrollFlow: Flow<FingerEnrollStateViewModel> =
combine(_consumerShouldEnroll, _hardwareAuthToken, _enrollReason) {
consumerShouldEnroll,
hardwareAuthToken,
enrollReason ->
Triple(consumerShouldEnroll, hardwareAuthToken, enrollReason)
}
.transformLatest {
// transformLatest() instead of transform() is used here for cancelling previous enroll()
// whenever |consumerShouldEnroll| is changed. Otherwise the latest value will be suspended
// since enroll() is an infinite callback flow.
(consumerShouldEnroll, hardwareAuthToken, enrollReason) ->
if (consumerShouldEnroll && hardwareAuthToken != null) {
fingerprintManagerInteractor.enroll(hardwareAuthToken, enrollReason).collect { emit(it) }
}
}
.flowOn(backgroundDispatcher)
/** Used to indicate the consumer of the view model is ready for an enrollment. */
fun startEnroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) {
_enrollReason.update { enrollReason }
_hardwareAuthToken.update { hardwareAuthToken }
// Update _consumerShouldEnroll after updating the other values.
_consumerShouldEnroll.update { true }
}
/** Used to indicate to stop the enrollment. */
fun stopEnroll() {
_consumerShouldEnroll.update { false }
}
class FingerprintEnrollViewModelFactory(
val interactor: FingerprintManagerInteractor,
val backgroundDispatcher: CoroutineDispatcher
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
): T {
return FingerprintEnrollViewModel(interactor) as T
return FingerprintEnrollViewModel(interactor, backgroundDispatcher) as T
}
}
}

View File

@@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
const val TAG = "FingerprintEnrollmentNavigationViewModel"
private const val TAG = "FingerprintEnrollNavigationViewModel"
/** Interface to validate a gatekeeper hat */
interface Validator {
@@ -54,7 +54,7 @@ object Unicorn : EnrollType()
* This class is responsible for sending a [NavigationStep] which indicates where the user is in the
* Fingerprint Enrollment flow
*/
class FingerprintEnrollmentNavigationViewModel(
class FingerprintEnrollNavigationViewModel(
private val dispatcher: CoroutineDispatcher,
private val validator: Validator,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
@@ -131,7 +131,7 @@ class FingerprintEnrollmentNavigationViewModel(
}
}
class FingerprintEnrollmentNavigationViewModelFactory(
class FingerprintEnrollNavigationViewModelFactory(
private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
@@ -143,7 +143,7 @@ class FingerprintEnrollmentNavigationViewModel(
modelClass: Class<T>,
): T {
return FingerprintEnrollmentNavigationViewModel(
return FingerprintEnrollNavigationViewModel(
backgroundDispatcher,
object : Validator {
override fun validateGateKeeper(challenge: Long?): Boolean {

View File

@@ -29,6 +29,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
private const val TAG = "FingerprintGatekeeperViewModel"
sealed interface GatekeeperInfo {
object Invalid : GatekeeperInfo
object Timeout : GatekeeperInfo

View File

@@ -22,6 +22,8 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
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.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@@ -32,6 +34,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
var pressToAuthEnabled = true
var sensorProps =
@@ -68,6 +71,11 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
override suspend fun enroll(
hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason
): Flow<FingerEnrollStateViewModel> = flow { emit(enrollStateViewModel) }
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
return enrolledFingerprintsInternal.remove(fp)
}

View File

@@ -30,6 +30,8 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
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.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.EnrollReason.FindSensor
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerEnrollStateViewModel
import com.android.settings.password.ChooseLockSettingsHelper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelAndJoin
@@ -143,7 +145,7 @@ class FingerprintManagerInteractorTest {
.thenReturn(byteArray)
val generateChallengeCallback: ArgumentCaptor<FingerprintManager.GenerateChallengeCallback> =
ArgumentCaptor.forClass(FingerprintManager.GenerateChallengeCallback::class.java)
argumentCaptor()
var result: Pair<Long, ByteArray?>? = null
val job = testScope.launch { result = underTest.generateChallenge(1L) }
@@ -165,8 +167,7 @@ class FingerprintManagerInteractorTest {
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
var result: Boolean? = null
val job =
@@ -189,8 +190,7 @@ class FingerprintManagerInteractorTest {
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
var result: Boolean? = null
val job =
@@ -229,8 +229,7 @@ class FingerprintManagerInteractorTest {
var result: FingerprintAuthAttemptViewModel? = null
val job = launch { result = underTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
runCurrent()
@@ -257,8 +256,7 @@ class FingerprintManagerInteractorTest {
var result: FingerprintAuthAttemptViewModel? = null
val job = launch { result = underTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
runCurrent()
@@ -280,8 +278,82 @@ class FingerprintManagerInteractorTest {
)
}
@Test
fun testEnroll_progress() =
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null
val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
verify(fingerprintManager)
.enroll(
eq(token),
any(CancellationSignal::class.java),
anyInt(),
capture(enrollCallback),
eq(FingerprintManager.ENROLL_FIND_SENSOR)
)
enrollCallback.value.onEnrollmentProgress(1)
runCurrent()
job.cancelAndJoin()
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollProgress(1))
}
@Test
fun testEnroll_help() =
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null
val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
verify(fingerprintManager)
.enroll(
eq(token),
any(CancellationSignal::class.java),
anyInt(),
capture(enrollCallback),
eq(FingerprintManager.ENROLL_FIND_SENSOR)
)
enrollCallback.value.onEnrollmentHelp(-1, "help")
runCurrent()
job.cancelAndJoin()
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollHelp(-1, "help"))
}
@Test
fun testEnroll_error() =
testScope.runTest {
val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null
val job = launch { underTest.enroll(token, FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent()
verify(fingerprintManager)
.enroll(
eq(token),
any(CancellationSignal::class.java),
anyInt(),
capture(enrollCallback),
eq(FingerprintManager.ENROLL_FIND_SENSOR)
)
enrollCallback.value.onEnrollmentError(-2, "error")
runCurrent()
job.cancelAndJoin()
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollError(-2, "error"))
}
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
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> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
ArgumentCaptor.forClass(T::class.java)
}