Add enroll() in FingerprintManagerInteractor.
Test: atest FingerprintManagerInteractorTest Bug: 295206773 Change-Id: If2fc46b1c952c3e55c698a18e125e194efe5ffb6
This commit is contained in:
@@ -27,12 +27,18 @@ import android.util.Log
|
|||||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
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 com.android.settings.password.ChooseLockSettingsHelper
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlinx.coroutines.CancellableContinuation
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.channels.onFailure
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -66,6 +72,16 @@ interface FingerprintManagerInteractor {
|
|||||||
*/
|
*/
|
||||||
suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
|
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
|
* Removes the given fingerprint, returning true if it was successfully removed and false
|
||||||
* otherwise
|
* otherwise
|
||||||
@@ -133,6 +149,51 @@ class FingerprintManagerInteractorImpl(
|
|||||||
|
|
||||||
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
|
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 {
|
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
|
||||||
val callback =
|
val callback =
|
||||||
object : RemovalCallback() {
|
object : RemovalCallback() {
|
||||||
|
@@ -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.FingerprintEnrollConfirmationV2Fragment
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
|
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.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.Confirmation
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
|
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.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.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.FingerprintGatekeeperViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
|
||||||
@@ -70,7 +70,7 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
|
|||||||
* children fragments.
|
* children fragments.
|
||||||
*/
|
*/
|
||||||
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||||
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
|
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
|
||||||
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
||||||
private val coroutineDispatcher = Dispatchers.Default
|
private val coroutineDispatcher = Dispatchers.Default
|
||||||
|
|
||||||
@@ -170,18 +170,18 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
|||||||
navigationViewModel =
|
navigationViewModel =
|
||||||
ViewModelProvider(
|
ViewModelProvider(
|
||||||
this,
|
this,
|
||||||
FingerprintEnrollmentNavigationViewModel.FingerprintEnrollmentNavigationViewModelFactory(
|
FingerprintEnrollNavigationViewModel.FingerprintEnrollNavigationViewModelFactory(
|
||||||
backgroundDispatcher,
|
backgroundDispatcher,
|
||||||
interactor,
|
interactor,
|
||||||
gatekeeperViewModel,
|
gatekeeperViewModel,
|
||||||
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
|
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
|
||||||
)
|
)
|
||||||
)[FingerprintEnrollmentNavigationViewModel::class.java]
|
)[FingerprintEnrollNavigationViewModel::class.java]
|
||||||
|
|
||||||
// Initialize FingerprintViewModel
|
// Initialize FingerprintViewModel
|
||||||
ViewModelProvider(
|
ViewModelProvider(
|
||||||
this,
|
this,
|
||||||
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor)
|
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor, backgroundDispatcher)
|
||||||
)[FingerprintEnrollViewModel::class.java]
|
)[FingerprintEnrollViewModel::class.java]
|
||||||
|
|
||||||
// Initialize scroll view model
|
// Initialize scroll view model
|
||||||
@@ -198,7 +198,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
|||||||
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
|
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
|
||||||
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
|
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
|
||||||
Enrollment -> FingerprintEnrollEnrollingV2Fragment::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
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
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.
|
* A fragment to indicate that fingerprint enrollment has been completed.
|
||||||
@@ -33,7 +33,7 @@ class FingerprintEnrollConfirmationV2Fragment : Fragment() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val navigationViewModel =
|
val navigationViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
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. */
|
/** A fragment that is responsible for enrolling a users fingerprint. */
|
||||||
class FingerprintEnrollEnrollingV2Fragment : Fragment() {
|
class FingerprintEnrollEnrollingV2Fragment : Fragment() {
|
||||||
@@ -28,7 +28,7 @@ class FingerprintEnrollEnrollingV2Fragment : Fragment() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val navigationViewModel =
|
val navigationViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ import android.os.Bundle
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.android.settings.R
|
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.
|
* 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)
|
super.onCreate(savedInstanceState)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val navigationViewModel =
|
val navigationViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,8 +33,8 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.android.settings.R
|
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.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.FingerprintGatekeeperViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
|
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
|
* 2. How the data will be stored
|
||||||
* 3. How the user can access and remove their data
|
* 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 footerBarMixin: FooterBarMixin
|
||||||
private lateinit var textModel: TextModel
|
private lateinit var textModel: TextModel
|
||||||
private lateinit var navigationViewModel: FingerprintEnrollmentNavigationViewModel
|
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
|
||||||
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
||||||
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||||
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
|
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
|
||||||
@@ -83,7 +83,7 @@ class FingerprintEnrollmentIntroV2Fragment : Fragment(R.layout.fingerprint_v2_en
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
navigationViewModel =
|
navigationViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollmentNavigationViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
|
||||||
fingerprintEnrollViewModel =
|
fingerprintEnrollViewModel =
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
|
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java]
|
||||||
fingerprintScrollViewModel =
|
fingerprintScrollViewModel =
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -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()
|
||||||
|
}
|
@@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.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.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
||||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||||
import com.android.systemui.biometrics.shared.model.toSensorType
|
import com.android.systemui.biometrics.shared.model.toSensorType
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.Flow
|
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.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
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. */
|
private const val TAG = "FingerprintEnrollViewModel"
|
||||||
class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
|
|
||||||
ViewModel() {
|
/** 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] */
|
/** Represents the stream of [FingerprintSensorType] */
|
||||||
val sensorType: Flow<FingerprintSensorType> =
|
val sensorType: Flow<FingerprintSensorType> =
|
||||||
@@ -35,14 +45,55 @@ class FingerprintEnrollViewModel(fingerprintManagerInteractor: FingerprintManage
|
|||||||
it.sensorType.toSensorType()
|
it.sensorType.toSensorType()
|
||||||
}
|
}
|
||||||
|
|
||||||
class FingerprintEnrollViewModelFactory(val interactor: FingerprintManagerInteractor) :
|
private var _enrollReason: MutableStateFlow<EnrollReason> =
|
||||||
ViewModelProvider.Factory {
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel> create(
|
override fun <T : ViewModel> create(
|
||||||
modelClass: Class<T>,
|
modelClass: Class<T>,
|
||||||
): T {
|
): T {
|
||||||
return FingerprintEnrollViewModel(interactor) as T
|
return FingerprintEnrollViewModel(interactor, backgroundDispatcher) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.filterNotNull
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
const val TAG = "FingerprintEnrollmentNavigationViewModel"
|
private const val TAG = "FingerprintEnrollNavigationViewModel"
|
||||||
|
|
||||||
/** Interface to validate a gatekeeper hat */
|
/** Interface to validate a gatekeeper hat */
|
||||||
interface Validator {
|
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
|
* This class is responsible for sending a [NavigationStep] which indicates where the user is in the
|
||||||
* Fingerprint Enrollment flow
|
* Fingerprint Enrollment flow
|
||||||
*/
|
*/
|
||||||
class FingerprintEnrollmentNavigationViewModel(
|
class FingerprintEnrollNavigationViewModel(
|
||||||
private val dispatcher: CoroutineDispatcher,
|
private val dispatcher: CoroutineDispatcher,
|
||||||
private val validator: Validator,
|
private val validator: Validator,
|
||||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||||
@@ -131,7 +131,7 @@ class FingerprintEnrollmentNavigationViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FingerprintEnrollmentNavigationViewModelFactory(
|
class FingerprintEnrollNavigationViewModelFactory(
|
||||||
private val backgroundDispatcher: CoroutineDispatcher,
|
private val backgroundDispatcher: CoroutineDispatcher,
|
||||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||||
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
|
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||||
@@ -143,7 +143,7 @@ class FingerprintEnrollmentNavigationViewModel(
|
|||||||
modelClass: Class<T>,
|
modelClass: Class<T>,
|
||||||
): T {
|
): T {
|
||||||
|
|
||||||
return FingerprintEnrollmentNavigationViewModel(
|
return FingerprintEnrollNavigationViewModel(
|
||||||
backgroundDispatcher,
|
backgroundDispatcher,
|
||||||
object : Validator {
|
object : Validator {
|
||||||
override fun validateGateKeeper(challenge: Long?): Boolean {
|
override fun validateGateKeeper(challenge: Long?): Boolean {
|
@@ -29,6 +29,8 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private const val TAG = "FingerprintGatekeeperViewModel"
|
||||||
|
|
||||||
sealed interface GatekeeperInfo {
|
sealed interface GatekeeperInfo {
|
||||||
object Invalid : GatekeeperInfo
|
object Invalid : GatekeeperInfo
|
||||||
object Timeout : GatekeeperInfo
|
object Timeout : GatekeeperInfo
|
||||||
|
@@ -22,6 +22,8 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
|||||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
|
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.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
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
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
|||||||
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
|
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
|
||||||
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
|
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
|
||||||
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
|
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
|
||||||
|
val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
|
||||||
var pressToAuthEnabled = true
|
var pressToAuthEnabled = true
|
||||||
|
|
||||||
var sensorProps =
|
var sensorProps =
|
||||||
@@ -68,6 +71,11 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
|||||||
|
|
||||||
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
|
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 {
|
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
|
||||||
return enrolledFingerprintsInternal.remove(fp)
|
return enrolledFingerprintsInternal.remove(fp)
|
||||||
}
|
}
|
||||||
|
@@ -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.domain.interactor.FingerprintManagerInteractorImpl
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
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.android.settings.password.ChooseLockSettingsHelper
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
@@ -143,7 +145,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
.thenReturn(byteArray)
|
.thenReturn(byteArray)
|
||||||
|
|
||||||
val generateChallengeCallback: ArgumentCaptor<FingerprintManager.GenerateChallengeCallback> =
|
val generateChallengeCallback: ArgumentCaptor<FingerprintManager.GenerateChallengeCallback> =
|
||||||
ArgumentCaptor.forClass(FingerprintManager.GenerateChallengeCallback::class.java)
|
argumentCaptor()
|
||||||
|
|
||||||
var result: Pair<Long, ByteArray?>? = null
|
var result: Pair<Long, ByteArray?>? = null
|
||||||
val job = testScope.launch { result = underTest.generateChallenge(1L) }
|
val job = testScope.launch { result = underTest.generateChallenge(1L) }
|
||||||
@@ -165,8 +167,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
||||||
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
||||||
|
|
||||||
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
|
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
|
||||||
ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
|
|
||||||
|
|
||||||
var result: Boolean? = null
|
var result: Boolean? = null
|
||||||
val job =
|
val job =
|
||||||
@@ -189,8 +190,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
||||||
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
||||||
|
|
||||||
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
|
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
|
||||||
ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
|
|
||||||
|
|
||||||
var result: Boolean? = null
|
var result: Boolean? = null
|
||||||
val job =
|
val job =
|
||||||
@@ -229,8 +229,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
var result: FingerprintAuthAttemptViewModel? = null
|
var result: FingerprintAuthAttemptViewModel? = null
|
||||||
val job = launch { result = underTest.authenticate() }
|
val job = launch { result = underTest.authenticate() }
|
||||||
|
|
||||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
|
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
|
||||||
ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
|
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
||||||
@@ -257,8 +256,7 @@ class FingerprintManagerInteractorTest {
|
|||||||
var result: FingerprintAuthAttemptViewModel? = null
|
var result: FingerprintAuthAttemptViewModel? = null
|
||||||
val job = launch { result = underTest.authenticate() }
|
val job = launch { result = underTest.authenticate() }
|
||||||
|
|
||||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
|
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
|
||||||
ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
|
|
||||||
|
|
||||||
runCurrent()
|
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 : Any> safeEq(value: T): T = eq(value) ?: value
|
||||||
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
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> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||||
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
|
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
|
||||||
|
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
|
||||||
|
ArgumentCaptor.forClass(T::class.java)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user