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.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() {
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 =
|
@@ -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
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user