Rear Fingerprint Enrollment

Bug: 297083009
Test: atest RFPSIconTouchViewModelTest FingerprintEnrollEnrollingViewModelTest FingerprintManagerInteractorTest
Change-Id: Icc072e7d7815070087ccb50ea5937c386b06fb11
This commit is contained in:
Joshua McCloskey
2023-10-02 18:20:17 +00:00
parent f29e758da9
commit a98dc8d4b5
41 changed files with 1935 additions and 378 deletions

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?attr/fingerprint_layout_theme"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<com.google.android.setupdesign.view.FillContentLayout
android:layout_width="@dimen/fingerprint_progress_bar_max_size"
android:layout_height="@dimen/fingerprint_progress_bar_max_size"
android:layout_marginVertical="24dp"
android:paddingTop="0dp"
android:paddingBottom="0dp">
<com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fingerprint_progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/fp_illustration"
android:minHeight="@dimen/fingerprint_progress_bar_min_size"
android:progress="0" />
</com.google.android.setupdesign.view.FillContentLayout>
<TextView
android:id="@+id/text"
style="@style/TextAppearance.ErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:accessibilityLiveRegion="polite"
android:gravity="center"
android:visibility="invisible" />
</LinearLayout>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>

View File

@@ -16,14 +16,61 @@
package com.android.settings.biometrics.fingerprint2.conversion package com.android.settings.biometrics.fingerprint2.conversion
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
class Util object Util {
fun EnrollReason.toOriginalReason(): Int {
fun EnrollReason.toOriginalReason(): Int { return when (this) {
return when (this) { EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR }
} }
fun Int.toEnrollError(isSetupWizard: Boolean): FingerEnrollState.EnrollError {
val errTitle =
when (this) {
FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
R.string.security_settings_fingerprint_enroll_error_dialog_title
FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
R.string.security_settings_fingerprint_bad_calibration_title
else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
}
val errString =
if (isSetupWizard) {
when (this) {
FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
R.string.security_settings_fingerprint_enroll_error_dialog_title
FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
R.string.security_settings_fingerprint_bad_calibration_title
else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
}
} else {
when (this) {
// This message happens when the underlying crypto layer
// decides to revoke the enrollment auth token
FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message
FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
R.string.security_settings_fingerprint_bad_calibration
FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS ->
R.string.security_settings_fingerprint_enroll_error_unable_to_process_message
// There's nothing specific to tell the user about. Ask them to try again.
else -> R.string.security_settings_fingerprint_enroll_error_generic_dialog_message
}
}
return FingerEnrollState.EnrollError(
errTitle,
errString,
this == FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
this == FINGERPRINT_ERROR_CANCELED,
)
}
} }

View File

@@ -24,12 +24,16 @@ import android.hardware.fingerprint.FingerprintManager.RemovalCallback
import android.os.CancellationSignal import android.os.CancellationSignal
import android.util.Log import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.conversion.toOriginalReason import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
import com.android.settings.password.ChooseLockSettingsHelper import com.android.settings.password.ChooseLockSettingsHelper
import com.android.systemui.biometrics.shared.model.toFingerprintSensor import com.android.systemui.biometrics.shared.model.toFingerprintSensor
import kotlin.coroutines.resume import kotlin.coroutines.resume
@@ -38,9 +42,12 @@ import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -51,7 +58,8 @@ class FingerprintManagerInteractorImpl(
private val backgroundDispatcher: CoroutineDispatcher, private val backgroundDispatcher: CoroutineDispatcher,
private val fingerprintManager: FingerprintManager, private val fingerprintManager: FingerprintManager,
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider, private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
private val pressToAuthProvider: () -> Boolean, private val pressToAuthProvider: PressToAuthProvider,
private val fingerprintFlow: FingerprintFlow,
) : FingerprintManagerInteractor { ) : FingerprintManagerInteractor {
private val maxFingerprints = private val maxFingerprints =
@@ -60,6 +68,8 @@ class FingerprintManagerInteractorImpl(
) )
private val applicationContext = applicationContext.applicationContext private val applicationContext = applicationContext.applicationContext
private val enrollRequestOutstanding = MutableStateFlow(false)
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> = override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
suspendCoroutine { suspendCoroutine {
val callback = GenerateChallengeCallback { _, userId, challenge -> val callback = GenerateChallengeCallback { _, userId, challenge ->
@@ -75,11 +85,11 @@ class FingerprintManagerInteractorImpl(
fingerprintManager.generateChallenge(applicationContext.userId, callback) fingerprintManager.generateChallenge(applicationContext.userId, callback)
} }
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow { override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
emit( emit(
fingerprintManager fingerprintManager
.getEnrolledFingerprints(applicationContext.userId) .getEnrolledFingerprints(applicationContext.userId)
.map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) } .map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
.toList() .toList()
) )
} }
@@ -103,28 +113,51 @@ class FingerprintManagerInteractorImpl(
override suspend fun enroll( override suspend fun enroll(
hardwareAuthToken: ByteArray?, hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason, enrollReason: EnrollReason,
): Flow<FingerEnrollStateViewModel> = callbackFlow { ): Flow<FingerEnrollState> = callbackFlow {
// TODO (b/308456120) Improve this logic
if (enrollRequestOutstanding.value) {
Log.d(TAG, "Outstanding enroll request, waiting 150ms")
delay(150)
if (enrollRequestOutstanding.value) {
Log.e(TAG, "Request still present, continuing")
}
}
enrollRequestOutstanding.update { true }
var streamEnded = false var streamEnded = false
var totalSteps: Int? = null
val enrollmentCallback = val enrollmentCallback =
object : FingerprintManager.EnrollmentCallback() { object : FingerprintManager.EnrollmentCallback() {
override fun onEnrollmentProgress(remaining: Int) { override fun onEnrollmentProgress(remaining: Int) {
trySend(FingerEnrollStateViewModel.EnrollProgress(remaining)).onFailure { error -> // This is sort of an implementation detail, but unfortunately the API isn't
// very expressive. If anything we should look at changing the FingerprintManager API.
if (totalSteps == null) {
totalSteps = remaining + 1
}
trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure {
error ->
Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error") Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
} }
if (remaining == 0) { if (remaining == 0) {
streamEnded = true streamEnded = true
enrollRequestOutstanding.update { false }
} }
} }
override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) { override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
trySend(FingerEnrollStateViewModel.EnrollHelp(helpMsgId, helpString.toString())) trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString()))
.onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") } .onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
} }
override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) { override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
trySend(FingerEnrollStateViewModel.EnrollError(errMsgId, errString.toString())) trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard))
.onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") } .onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
Log.d(TAG, "onEnrollmentError($errMsgId)")
streamEnded = true streamEnded = true
enrollRequestOutstanding.update { false }
} }
} }
@@ -140,12 +173,13 @@ class FingerprintManagerInteractorImpl(
// If the stream has not been ended, and the user has stopped collecting the flow // If the stream has not been ended, and the user has stopped collecting the flow
// before it was over, send cancel. // before it was over, send cancel.
if (!streamEnded) { if (!streamEnded) {
Log.e(TAG, "Cancel is sent from settings for enroll()")
cancellationSignal.cancel() cancellationSignal.cancel()
} }
} }
} }
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine { override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
val callback = val callback =
object : RemovalCallback() { object : RemovalCallback() {
override fun onRemovalError( override fun onRemovalError(
@@ -170,7 +204,7 @@ class FingerprintManagerInteractorImpl(
) )
} }
override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) { override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
withContext(backgroundDispatcher) { withContext(backgroundDispatcher) {
fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName) fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
} }
@@ -181,11 +215,11 @@ class FingerprintManagerInteractorImpl(
} }
override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine { override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
it.resume(pressToAuthProvider()) it.resume(pressToAuthProvider.isEnabled)
} }
override suspend fun authenticate(): FingerprintAuthAttemptViewModel = override suspend fun authenticate(): FingerprintAuthAttemptModel =
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> -> suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
val authenticationCallback = val authenticationCallback =
object : FingerprintManager.AuthenticationCallback() { object : FingerprintManager.AuthenticationCallback() {
@@ -195,7 +229,7 @@ class FingerprintManagerInteractorImpl(
Log.d(TAG, "framework sent down onAuthError after finish") Log.d(TAG, "framework sent down onAuthError after finish")
return return
} }
c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString())) c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
} }
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) { override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
@@ -204,7 +238,7 @@ class FingerprintManagerInteractorImpl(
Log.d(TAG, "framework sent down onAuthError after finish") Log.d(TAG, "framework sent down onAuthError after finish")
return return
} }
c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1)) c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
} }
} }

View File

@@ -0,0 +1,51 @@
/*
* 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.repository
import android.content.Context
import android.provider.Settings
import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
class PressToAuthProviderImpl(val context: Context) : PressToAuthProvider {
override val isEnabled: Boolean
get() {
var toReturn: Int =
Settings.Secure.getIntForUser(
context.contentResolver,
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-1,
context.userId,
)
if (toReturn == -1) {
toReturn =
if (
context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
) {
1
} else {
0
}
Settings.Secure.putIntForUser(
context.contentResolver,
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
toReturn,
context.userId
)
}
return (toReturn == 1)
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.shared.data.repository
/**
* Interface that indicates if press to auth is on or off.
*/
interface PressToAuthProvider {
/**
* Indicates true if the PressToAuth feature is enabled, false otherwise.
*/
val isEnabled: Boolean
}

View File

@@ -17,9 +17,9 @@
package com.android.settings.biometrics.fingerprint2.shared.domain.interactor package com.android.settings.biometrics.fingerprint2.shared.domain.interactor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.systemui.biometrics.shared.model.FingerprintSensor import com.android.systemui.biometrics.shared.model.FingerprintSensor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.Flow
*/ */
interface FingerprintManagerInteractor { interface FingerprintManagerInteractor {
/** Returns the list of current fingerprints. */ /** Returns the list of current fingerprints. */
val enrolledFingerprints: Flow<List<FingerprintViewModel>> val enrolledFingerprints: Flow<List<FingerprintData>>
/** Returns the max enrollable fingerprints, note during SUW this might be 1 */ /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
val maxEnrollableFingerprints: Flow<Int> val maxEnrollableFingerprints: Flow<Int>
@@ -43,7 +43,7 @@ interface FingerprintManagerInteractor {
val sensorPropertiesInternal: Flow<FingerprintSensor?> val sensorPropertiesInternal: Flow<FingerprintSensor?>
/** Runs the authenticate flow */ /** Runs the authenticate flow */
suspend fun authenticate(): FingerprintAuthAttemptViewModel suspend fun authenticate(): FingerprintAuthAttemptModel
/** /**
* Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
@@ -56,22 +56,22 @@ interface FingerprintManagerInteractor {
/** /**
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
* enrollment. Returning the [FingerEnrollStateViewModel] that represents this fingerprint * enrollment. Returning the [FingerEnrollState] that represents this fingerprint
* enrollment state. * enrollment state.
*/ */
suspend fun enroll( suspend fun enroll(
hardwareAuthToken: ByteArray?, hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason, enrollReason: EnrollReason,
): Flow<FingerEnrollStateViewModel> ): Flow<FingerEnrollState>
/** /**
* 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
*/ */
suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean suspend fun removeFingerprint(fp: FingerprintData): Boolean
/** Renames the given fingerprint if one exists */ /** Renames the given fingerprint if one exists */
suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) suspend fun renameFingerprint(fp: FingerprintData, newName: String)
/** Indicates if the device has side fingerprint */ /** Indicates if the device has side fingerprint */
suspend fun hasSideFps(): Boolean suspend fun hasSideFps(): Boolean

View File

@@ -22,19 +22,28 @@ import android.annotation.StringRes
* Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more * Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
* information * information
*/ */
sealed class FingerEnrollStateViewModel { sealed class FingerEnrollState {
/** Represents enrollment step progress. */ /**
* Represents an enrollment step progress.
*
* Progress is obtained by (totalStepsRequired - remainingSteps) / totalStepsRequired
*/
data class EnrollProgress( data class EnrollProgress(
val remainingSteps: Int, val remainingSteps: Int,
) : FingerEnrollStateViewModel() val totalStepsRequired: Int,
) : FingerEnrollState()
/** Represents that recoverable error has been encountered during enrollment. */ /** Represents that recoverable error has been encountered during enrollment. */
data class EnrollHelp( data class EnrollHelp(
@StringRes val helpMsgId: Int, @StringRes val helpMsgId: Int,
val helpString: String, val helpString: String,
) : FingerEnrollStateViewModel() ) : FingerEnrollState()
/** Represents that an unrecoverable error has been encountered and the operation is complete. */ /** Represents that an unrecoverable error has been encountered and the operation is complete. */
data class EnrollError( data class EnrollError(
@StringRes val errMsgId: Int, @StringRes val errTitle: Int,
val errString: String, @StringRes val errString: Int,
) : FingerEnrollStateViewModel() val shouldRetryEnrollment: Boolean,
val isCancelled: Boolean,
) : FingerEnrollState()
} }

View File

@@ -16,19 +16,19 @@
package com.android.settings.biometrics.fingerprint2.shared.model package com.android.settings.biometrics.fingerprint2.shared.model
data class FingerprintViewModel( data class FingerprintData(
val name: String, val name: String,
val fingerId: Int, val fingerId: Int,
val deviceId: Long, val deviceId: Long,
) )
sealed class FingerprintAuthAttemptViewModel { sealed class FingerprintAuthAttemptModel {
data class Success( data class Success(
val fingerId: Int, val fingerId: Int,
) : FingerprintAuthAttemptViewModel() ) : FingerprintAuthAttemptModel()
data class Error( data class Error(
val error: Int, val error: Int,
val message: String, val message: String,
) : FingerprintAuthAttemptViewModel() ) : FingerprintAuthAttemptModel()
} }

View File

@@ -0,0 +1,34 @@
/*
* 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.shared.model
/**
* The [FingerprintFlow] for fingerprint enrollment indicates information on how the flow should behave.
*/
sealed class FingerprintFlow
/** The default enrollment experience, typically called from Settings */
data object Default : FingerprintFlow()
/** SetupWizard/Out of box experience (OOBE) enrollment type. */
data object SetupWizard : FingerprintFlow()
/** Unicorn enrollment type */
data object Unicorn : FingerprintFlow()
/** Flow to specify settings type */
data object Settings : FingerprintFlow()

View File

@@ -16,12 +16,9 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity
import android.annotation.ColorInt
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
@@ -35,22 +32,27 @@ import androidx.lifecycle.lifecycleScope
import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils
import com.android.settings.R import com.android.settings.R
import com.android.settings.SetupWizardUtils import com.android.settings.SetupWizardUtils
import com.android.settings.Utils
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
import com.android.settings.biometrics.BiometricEnrollBase import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.GatekeeperPasswordProvider
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.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
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.FingerprintEnrollIntroV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
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.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel 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
@@ -65,8 +67,11 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Orie
import com.android.settings.password.ChooseLockGeneric import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper import com.android.settings.password.ChooseLockSettingsHelper
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.util.WizardManagerHelper
import com.google.android.setupdesign.util.ThemeHelper import com.google.android.setupdesign.util.ThemeHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -77,6 +82,7 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
* children fragments. * children fragments.
*/ */
class FingerprintEnrollmentV2Activity : FragmentActivity() { class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
@@ -84,6 +90,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var foldStateViewModel: FoldStateViewModel private lateinit var foldStateViewModel: FoldStateViewModel
private lateinit var orientationStateViewModel: OrientationStateViewModel private lateinit var orientationStateViewModel: OrientationStateViewModel
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var backgroundViewModel: BackgroundViewModel
private val coroutineDispatcher = Dispatchers.Default private val coroutineDispatcher = Dispatchers.Default
/** Result listener for ChooseLock activity flow. */ /** Result listener for ChooseLock activity flow. */
@@ -101,23 +108,22 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
} }
} }
override fun onAttachedToWindow() { override fun onStop() {
window.statusBarColor = getBackgroundColor() super.onStop()
super.onAttachedToWindow() if (!isChangingConfigurations) {
backgroundViewModel.wentToBackground()
}
} }
override fun onResume() {
super.onResume()
backgroundViewModel.inForeground()
}
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
foldStateViewModel.onConfigurationChange(newConfig) foldStateViewModel.onConfigurationChange(newConfig)
} }
@ColorInt
private fun getBackgroundColor(): Int {
val stateList: ColorStateList? =
Utils.getColorAttr(applicationContext, android.R.attr.windowBackground)
return stateList?.defaultColor ?: Color.TRANSPARENT
}
private fun onConfirmDevice(resultCode: Int, data: Intent?) { private fun onConfirmDevice(resultCode: Int, data: Intent?) {
val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long? val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
@@ -137,39 +143,28 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
val context = applicationContext val context = applicationContext
val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
val isAnySuw = WizardManagerHelper.isAnySetupWizard(intent)
val enrollType =
if (isAnySuw) {
SetupWizard
} else {
Default
}
backgroundViewModel =
ViewModelProvider(this, BackgroundViewModel.BackgroundViewModelFactory())[
BackgroundViewModel::class.java]
val interactor = val interactor =
FingerprintManagerInteractorImpl( FingerprintManagerInteractorImpl(
context, context,
backgroundDispatcher, backgroundDispatcher,
fingerprintManager, fingerprintManager,
GatekeeperPasswordProvider(LockPatternUtils(context)) GatekeeperPasswordProvider(LockPatternUtils(context)),
) { PressToAuthProviderImpl(context),
var toReturn: Int = enrollType,
Settings.Secure.getIntForUser( )
context.contentResolver,
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-1,
context.userId,
)
if (toReturn == -1) {
toReturn =
if (
context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
) {
1
} else {
0
}
Settings.Secure.putIntForUser(
context.contentResolver,
Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
toReturn,
context.userId
)
}
toReturn == 1
}
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long? var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
@@ -191,7 +186,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
backgroundDispatcher, backgroundDispatcher,
interactor, interactor,
gatekeeperViewModel, gatekeeperViewModel,
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */ gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo,
enrollType,
) )
)[FingerprintEnrollNavigationViewModel::class.java] )[FingerprintEnrollNavigationViewModel::class.java]
@@ -207,7 +203,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
this, this,
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory( FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
interactor, interactor,
backgroundDispatcher gatekeeperViewModel,
navigationViewModel,
) )
)[FingerprintEnrollViewModel::class.java] )[FingerprintEnrollViewModel::class.java]
@@ -230,6 +227,16 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[ ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
OrientationStateViewModel::class.java] OrientationStateViewModel::class.java]
// Initialize FingerprintEnrollEnrollingViewModel
fingerprintEnrollEnrollingViewModel =
ViewModelProvider(
this,
FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
fingerprintEnrollViewModel,
backgroundViewModel
)
)[FingerprintEnrollEnrollingViewModel::class.java]
// Initialize FingerprintEnrollFindSensorViewModel // Initialize FingerprintEnrollFindSensorViewModel
ViewModelProvider( ViewModelProvider(
this, this,
@@ -237,48 +244,65 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
navigationViewModel, navigationViewModel,
fingerprintEnrollViewModel, fingerprintEnrollViewModel,
gatekeeperViewModel, gatekeeperViewModel,
backgroundViewModel,
accessibilityViewModel, accessibilityViewModel,
foldStateViewModel, foldStateViewModel,
orientationStateViewModel orientationStateViewModel
) )
)[FingerprintEnrollFindSensorViewModel::class.java] )[FingerprintEnrollFindSensorViewModel::class.java]
// Initialize RFPS View Model
ViewModelProvider(
this,
RFPSViewModel.RFPSViewModelFactory(fingerprintEnrollEnrollingViewModel)
)[RFPSViewModel::class.java]
lifecycleScope.launch { lifecycleScope.launch {
navigationViewModel.navigationViewModel.filterNotNull().collect { navigationViewModel.navigationViewModel
Log.d(TAG, "navigationStep $it") .filterNotNull()
val isForward = it.forward .combine(fingerprintEnrollViewModel.sensorType) { nav, sensorType -> Pair(nav, sensorType) }
val currStep = it.currStep .collect { (nav, sensorType) ->
val theClass: Class<Fragment>? = Log.d(TAG, "navigationStep $nav")
when (currStep) { fingerprintEnrollViewModel.sensorTypeCached = sensorType
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment> val isForward = nav.forward
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment> val currStep = nav.currStep
Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment> val theClass: Class<Fragment>? =
Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment> when (currStep) {
else -> null Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
} Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
is Enrollment -> {
if (theClass != null) { when (sensorType) {
supportFragmentManager.fragments.onEach { fragment -> FingerprintSensorType.REAR -> RFPSEnrollFragment::class.java as Class<Fragment>
supportFragmentManager.beginTransaction().remove(fragment).commit() else -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
} }
supportFragmentManager }
.beginTransaction() Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
.setReorderingAllowed(true) else -> null
.add(R.id.fragment_container_view, theClass, null) }
.commit()
} else { if (theClass != null) {
supportFragmentManager.fragments.onEach { fragment ->
if (currStep is Finish) { supportFragmentManager.beginTransaction().remove(fragment).commit()
if (currStep.resultCode != null) { }
finishActivity(currStep.resultCode)
} else { supportFragmentManager
finish() .beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, theClass, null)
.commit()
} else {
if (currStep is Finish) {
if (currStep.resultCode != null) {
finishActivity(currStep.resultCode)
} else {
finish()
}
} else if (currStep == LaunchConfirmDeviceCredential) {
launchConfirmOrChooseLock(userId)
} }
} else if (currStep == LaunchConfirmDeviceCredential) {
launchConfirmOrChooseLock(userId)
} }
} }
}
} }
val fromSettingsSummary = val fromSettingsSummary =

View File

@@ -30,6 +30,7 @@ import com.android.settings.R
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton import com.google.android.setupcompat.template.FooterButton
@@ -54,23 +55,8 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
private var animation: FingerprintFindSensorAnimation? = null private var animation: FingerprintFindSensorAnimation? = null
private var contentLayoutId: Int = -1 private var contentLayoutId: Int = -1
private lateinit var viewModel: FingerprintEnrollFindSensorViewModel private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel =
ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
lifecycleScope.launch {
viewModel.sensorType.collect {
contentLayoutId =
when (it) {
FingerprintSensorType.UDFPS_OPTICAL,
FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
else -> R.layout.fingerprint_v2_enroll_find_sensor
}
}
}
} }
override fun onCreateView( override fun onCreateView(
@@ -78,6 +64,18 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val sensorType =
ViewModelProvider(requireActivity())[FingerprintEnrollViewModel::class.java].sensorTypeCached
contentLayoutId =
when (sensorType) {
FingerprintSensorType.UDFPS_OPTICAL,
FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
else -> R.layout.fingerprint_v2_enroll_find_sensor
}
return inflater.inflate(contentLayoutId, container, false).also { it -> return inflater.inflate(contentLayoutId, container, false).also { it ->
val view = it!! as GlifLayout val view = it!! as GlifLayout
@@ -106,7 +104,8 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
} }
lifecycleScope.launch { lifecycleScope.launch {
viewModel.showRfpsAnimation.collect { viewModel.showRfpsAnimation.collect {
animation = view.findViewById(R.id.fingerprint_sensor_location_animation) animation =
view.findViewById(R.id.fingerprint_sensor_location_animation)
animation!!.startAnimation() animation!!.startAnimation()
} }
} }

View File

@@ -36,11 +36,11 @@ 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.shared.model.Unicorn
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel 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.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.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.google.android.setupcompat.template.FooterBarMixin import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton import com.google.android.setupcompat.template.FooterButton
@@ -120,7 +120,7 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
combine( combine(
navigationViewModel.enrollType, navigationViewModel.fingerprintFlow,
fingerprintViewModel.sensorType, fingerprintViewModel.sensorType,
) { enrollType, sensorType -> ) { enrollType, sensorType ->
Pair(enrollType, sensorType) Pair(enrollType, sensorType)

View File

@@ -0,0 +1,26 @@
# Module enrollment
### Fingerprint Settings Enrollment Modules
This directory is responsible for containing the enrollment modules, each enrollment module is
responsible for the actual enrolling portion of FingerprintEnrollment.
The modules should be split out into udfps, rfps, and sfps.
[comment]: <> This file structure print out has been generated with the tree command.
```
├── enrolling
│   └── rfps
│   ├── data
│   ├── domain
│   │   └── RFPSInteractor.kt
│   ├── README.md
│   └── ui
│   ├── fragment
│   │   └── RFPSEnrollFragment.kt
│   ├── viewmodel
│   │   └── RFPSViewModel.kt
│   └── widget
│   └── RFPSProgressIndicator.kt
└── README.md
```

View File

@@ -0,0 +1,252 @@
/*
* 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.modules.enrolling.rfps.ui.fragment
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.view.animation.Interpolator
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import com.google.android.setupcompat.template.FooterBarMixin
import com.google.android.setupcompat.template.FooterButton
import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
private const val TAG = "RFPSEnrollFragment"
/** This fragment is responsible for taking care of rear fingerprint enrollment. */
class RFPSEnrollFragment : Fragment(R.layout.fingerprint_v2_rfps_enroll_enrolling) {
private lateinit var linearOutSlowInInterpolator: Interpolator
private lateinit var fastOutLinearInInterpolator: Interpolator
private lateinit var textView: TextView
private lateinit var progressBar: RFPSProgressBar
private val iconTouchViewModel: RFPSIconTouchViewModel by lazy {
ViewModelProvider(requireActivity())[RFPSIconTouchViewModel::class.java]
}
private val orientationViewModel: OrientationStateViewModel by lazy {
ViewModelProvider(requireActivity())[OrientationStateViewModel::class.java]
}
private val rfpsViewModel: RFPSViewModel by lazy {
ViewModelProvider(requireActivity())[RFPSViewModel::class.java]
}
private val backgroundViewModel: BackgroundViewModel by lazy {
ViewModelProvider(requireActivity())[BackgroundViewModel::class.java]
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)!!
val fragment = this
val context = requireContext()
val glifLayout = view.requireViewById(R.id.setup_wizard_layout) as GlifLayout
glifLayout.setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message)
glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title)
fastOutLinearInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_linear_in)
linearOutSlowInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in)
textView = view.requireViewById(R.id.text) as TextView
progressBar = view.requireViewById(R.id.fingerprint_progress_bar) as RFPSProgressBar
val footerBarMixin = glifLayout.getMixin(FooterBarMixin::class.java)
footerBarMixin.secondaryButton =
FooterButton.Builder(context)
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
.setListener { Log.e(TAG, "skip enrollment!") }
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build()
footerBarMixin.buttonContainer.setBackgroundColor(Color.TRANSPARENT)
progressBar.setOnTouchListener { _, motionEvent ->
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
iconTouchViewModel.userTouchedFingerprintIcon()
}
true
}
// On any orientation event, dismiss dialogs.
viewLifecycleOwner.lifecycleScope.launch {
orientationViewModel.orientation.collect { dismissDialogs() }
}
// Signal we are ready for enrollment.
rfpsViewModel.readyForEnrollment()
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
// Icon animation update
viewLifecycleOwner.lifecycleScope.launch {
rfpsViewModel.shouldAnimateIcon.collect { animate ->
progressBar.updateIconAnimation(animate)
}
}
// Flow to show a dialog.
viewLifecycleOwner.lifecycleScope.launch {
iconTouchViewModel.shouldShowDialog.collectLatest { showDialog ->
if (showDialog) {
try {
IconTouchDialog.showInstance(fragment)
} catch (exception: Exception) {
Log.d(TAG, "Dialog dismissed due to $exception")
}
}
}
}
}
}
// If we go to the background, then finish enrollment. This should be permanent finish,
// and shouldn't be reset until we explicitly tell the view model we want to retry
// enrollment.
viewLifecycleOwner.lifecycleScope.launch {
backgroundViewModel.background
.filter { inBackground -> inBackground }
.collect { rfpsViewModel.stopEnrollment() }
}
viewLifecycleOwner.lifecycleScope.launch {
rfpsViewModel.progress.filterNotNull().collect { progress -> handleEnrollProgress(progress) }
}
viewLifecycleOwner.lifecycleScope.launch {
rfpsViewModel.helpMessage.filterNotNull().collect { help ->
textView.text = help.helpString
textView.visibility = View.VISIBLE
textView.translationY =
resources.getDimensionPixelSize(R.dimen.fingerprint_error_text_appear_distance).toFloat()
textView.alpha = 0f
textView
.animate()
.alpha(1f)
.translationY(0f)
.setDuration(200)
.setInterpolator(linearOutSlowInInterpolator)
.start()
}
}
viewLifecycleOwner.lifecycleScope.launch {
rfpsViewModel.errorMessage.filterNotNull().collect { error -> handleEnrollError(error) }
}
viewLifecycleOwner.lifecycleScope.launch {
rfpsViewModel.textViewIsVisible.collect {
textView.visibility = if (it) View.VISIBLE else View.INVISIBLE
}
}
viewLifecycleOwner.lifecycleScope.launch {
rfpsViewModel.clearHelpMessage.collect {
textView
.animate()
.alpha(0f)
.translationY(
resources
.getDimensionPixelSize(R.dimen.fingerprint_error_text_disappear_distance)
.toFloat()
)
.setDuration(100)
.setInterpolator(fastOutLinearInInterpolator)
.withEndAction { rfpsViewModel.setVisibility(false) }
.start()
}
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.DESTROYED) {
rfpsViewModel.stopEnrollment()
dismissDialogs()
}
}
return view
}
private fun handleEnrollError(error: FingerEnrollState.EnrollError) {
val fragment = this
viewLifecycleOwner.lifecycleScope.launch {
try {
val shouldRestartEnrollment = FingerprintErrorDialog.showInstance(error, fragment)
} catch (exception: Exception) {
Log.e(TAG, "Exception occurred $exception")
}
onEnrollmentFailed()
}
}
private fun onEnrollmentFailed() {
rfpsViewModel.stopEnrollment()
}
private fun handleEnrollProgress(progress: FingerEnrollState.EnrollProgress) {
progressBar.updateProgress(
progress.remainingSteps.toFloat() / progress.totalStepsRequired.toFloat()
)
if (progress.remainingSteps == 0) {
performNextStepSuccess()
}
}
private fun performNextStepSuccess() {}
private fun dismissDialogs() {
val transaction = parentFragmentManager.beginTransaction()
for (frag in parentFragmentManager.fragments) {
if (frag is InstrumentedDialogFragment) {
Log.d(TAG, "removing dialog settings fragment $frag")
frag.dismiss()
transaction.remove(frag)
}
}
transaction.commitAllowingStateLoss()
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.modules.enrolling.rfps.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
private const val touchesToShowDialog = 3
/**
* This class is responsible for counting the number of touches on the fingerprint icon, and if this
* number reaches a threshold it will produce an action via [shouldShowDialog] to indicate the ui
* should show a dialog.
*/
class RFPSIconTouchViewModel : ViewModel() {
/** Keeps the number of times a user has touches the fingerprint icon. */
private val _touches: MutableStateFlow<Int> = MutableStateFlow(0)
/**
* Whether or not the UI should be showing the dialog. By making this SharingStarted.Eagerly
* the first event 0 % 3 == 0 will fire as soon as this view model is created, so it should
* be ignored and work as intended.
*/
val shouldShowDialog: Flow<Boolean> =
_touches
.transform { numTouches -> emit((numTouches % touchesToShowDialog) == 0) }
.shareIn(viewModelScope, SharingStarted.Eagerly, 0)
/** Indicates a user has tapped on the fingerprint icon. */
fun userTouchedFingerprintIcon() {
_touches.update { _touches.value + 1 }
}
class RFPSIconTouchViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RFPSIconTouchViewModel() as T
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.modules.enrolling.rfps.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
/** View Model used by the rear fingerprint enrollment fragment. */
class RFPSViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
) : ViewModel() {
/** Value to indicate if the text view is visible or not **/
private val _textViewIsVisible = MutableStateFlow<Boolean>(false)
val textViewIsVisible: Flow<Boolean> = _textViewIsVisible.asStateFlow()
/** Indicates if the icon should be animating or not */
val shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
private val enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFLow
/**
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
* recent state (this is useful for things like screen rotation)
*/
val progress: Flow<FingerEnrollState.EnrollProgress?> =
enrollFlow
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
/** Clear help message on enroll progress */
val clearHelpMessage: Flow<Boolean> = progress.map { it != null }
/** Enroll help message that is only displayed once */
val helpMessage: Flow<FingerEnrollState.EnrollHelp?> =
enrollFlow
.filterIsInstance<FingerEnrollState.EnrollHelp>()
.shareIn(viewModelScope, SharingStarted.Eagerly, 0).transform {
_textViewIsVisible.update { true }
}
/**
* The error message should only be shown once, for scenarios like screen rotations, we don't want
* to re-show the error message.
*/
val errorMessage: Flow<FingerEnrollState.EnrollError?> =
enrollFlow
.filterIsInstance<FingerEnrollState.EnrollError>()
.shareIn(viewModelScope, SharingStarted.Eagerly, 0)
/** Indicates if the consumer is ready for enrollment */
fun readyForEnrollment() {
fingerprintEnrollViewModel.canEnroll()
}
/** Indicates if enrollment should stop */
fun stopEnrollment() {
fingerprintEnrollViewModel.stopEnroll()
}
fun setVisibility(isVisible: Boolean) {
_textViewIsVisible.update { isVisible }
}
class RFPSViewModelFactory(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
): T {
return RFPSViewModel(fingerprintEnrollEnrollingViewModel) as T
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.modules.enrolling.rfps.ui.widget
import android.app.AlertDialog
import android.app.Dialog
import android.app.settings.SettingsEnums
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
private const val TAG = "FingerprintErrorDialog"
/** A Dialog used for fingerprint enrollment when an error occurs. */
class FingerprintErrorDialog : InstrumentedDialogFragment() {
private lateinit var onContinue: DialogInterface.OnClickListener
private lateinit var onTryAgain: DialogInterface.OnClickListener
private lateinit var onCancelListener: DialogInterface.OnCancelListener
override fun onCancel(dialog: DialogInterface) {
Log.d(TAG, "onCancel $dialog")
onCancelListener.onCancel(dialog)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog $this")
val errorString = requireArguments().getInt(KEY_MESSAGE)
val errorTitle = requireArguments().getInt(KEY_TITLE)
val builder = AlertDialog.Builder(requireContext())
val shouldShowTryAgain = requireArguments().getBoolean(KEY_SHOULD_TRY_AGAIN)
builder.setTitle(errorTitle).setMessage(errorString).setCancelable(false)
if (shouldShowTryAgain) {
builder
.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_try_again) {
dialog,
which ->
dialog.dismiss()
onTryAgain.onClick(dialog, which)
}
.setNegativeButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which
->
dialog.dismiss()
onContinue.onClick(dialog, which)
}
} else {
builder.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) {
dialog,
which ->
dialog.dismiss()
onContinue.onClick(dialog, which)
}
}
val dialog = builder.create()
dialog.setCanceledOnTouchOutside(false)
return dialog
}
override fun getMetricsCategory(): Int {
return SettingsEnums.DIALOG_FINGERPINT_ERROR
}
companion object {
private const val KEY_MESSAGE = "fingerprint_message"
private const val KEY_TITLE = "fingerprint_title"
private const val KEY_SHOULD_TRY_AGAIN = "should_try_again"
suspend fun showInstance(
error: FingerEnrollState.EnrollError,
fragment: Fragment,
) = suspendCancellableCoroutine { continuation ->
val dialog = FingerprintErrorDialog()
dialog.onTryAgain = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
dialog.onContinue = DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
dialog.onCancelListener =
DialogInterface.OnCancelListener {
Log.d(TAG, "onCancelListener clicked $dialog")
continuation.resume(null)
}
continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
val bundle = Bundle()
bundle.putInt(
KEY_TITLE,
error.errTitle,
)
bundle.putInt(
KEY_MESSAGE,
error.errString,
)
bundle.putBoolean(
KEY_SHOULD_TRY_AGAIN,
error.shouldRetryEnrollment,
)
dialog.arguments = bundle
Log.d(TAG, "showing dialog $dialog")
dialog.show(fragment.parentFragmentManager, FingerprintErrorDialog::class.java.toString())
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.modules.enrolling.rfps.ui.widget
import android.app.AlertDialog
import android.app.Dialog
import android.app.settings.SettingsEnums
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import com.android.settings.R
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
private const val TAG = "IconTouchDialog"
/** Dialog shown when the user taps the Progress bar a certain amount of times. */
class IconTouchDialog : InstrumentedDialogFragment() {
lateinit var onDismissListener: DialogInterface.OnClickListener
lateinit var onCancelListener: DialogInterface.OnCancelListener
override fun onCancel(dialog: DialogInterface) {
Log.d(TAG, "onCancel $dialog")
onCancelListener.onCancel(dialog)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder: AlertDialog.Builder = AlertDialog.Builder(activity, R.style.Theme_AlertDialog)
builder
.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
.setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
.setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok) { dialog, which ->
dialog.dismiss()
onDismissListener.onClick(dialog, which)
}
.setOnCancelListener { onCancelListener.onCancel(it) }
return builder.create()
}
override fun getMetricsCategory(): Int {
return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH
}
companion object {
suspend fun showInstance(fragment: Fragment) = suspendCancellableCoroutine { continuation ->
val dialog = IconTouchDialog()
dialog.onDismissListener =
DialogInterface.OnClickListener { _, _ -> continuation.resume("Done") }
dialog.onCancelListener =
DialogInterface.OnCancelListener { _ -> continuation.resume("OnCancel") }
continuation.invokeOnCancellation { Log.d(TAG, "invokeOnCancellation $dialog") }
dialog.show(fragment.parentFragmentManager, IconTouchDialog::class.java.toString())
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.modules.enrolling.rfps.ui.widget
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.util.AttributeSet
import android.view.animation.AnimationUtils
import android.view.animation.Interpolator
import com.android.settings.R
import com.android.settings.widget.RingProgressBar
/** Progress bar for rear fingerprint enrollment. */
class RFPSProgressBar(context: Context, attributeSet: AttributeSet) :
RingProgressBar(context, attributeSet) {
private val fastOutSlowInInterpolator: Interpolator
private val iconAnimationDrawable: AnimatedVectorDrawable
private val iconBackgroundBlinksDrawable: AnimatedVectorDrawable
private val maxProgress: Int
private var progressAnimation: ObjectAnimator? = null
private var shouldAnimateInternal: Boolean = true
init {
val fingerprintDrawable = background as LayerDrawable
iconAnimationDrawable =
fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation)
as AnimatedVectorDrawable
iconBackgroundBlinksDrawable =
fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background)
as AnimatedVectorDrawable
fastOutSlowInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in)
iconAnimationDrawable.registerAnimationCallback(
object : Animatable2.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
super.onAnimationEnd(drawable)
if (shouldAnimateInternal) {
animateIconAnimationInternal()
}
}
}
)
animateIconAnimationInternal()
progressBackgroundTintMode = PorterDuff.Mode.SRC
val attributes =
context.obtainStyledAttributes(R.style.RingProgressBarStyle, intArrayOf(android.R.attr.max))
maxProgress = attributes.getInt(0, -1)
attributes.recycle()
}
/** Indicates if the progress animation should be running */
fun updateIconAnimation(shouldAnimate: Boolean) {
if (shouldAnimate && !shouldAnimateInternal) {
animateIconAnimationInternal()
}
shouldAnimateInternal = shouldAnimate
}
/** This function should only be called when actual progress has been made. */
fun updateProgress(percentComplete: Float) {
val progress = maxProgress - (percentComplete.coerceIn(0.0f, 100.0f) * maxProgress).toInt()
iconBackgroundBlinksDrawable.start()
progressAnimation?.isRunning?.let { progressAnimation!!.cancel() }
progressAnimation = ObjectAnimator.ofInt(this, "progress", getProgress(), progress)
progressAnimation?.interpolator = fastOutSlowInInterpolator
progressAnimation?.setDuration(250)
progressAnimation?.start()
}
private fun animateIconAnimationInternal() {
iconAnimationDrawable.start()
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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 androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
/** A class for determining if the application is in the background or not. */
class BackgroundViewModel : ViewModel() {
private val _background = MutableStateFlow(false)
/** When true, the application is in background, else false */
val background = _background.asStateFlow()
/** Indicates that the application has been put in the background. */
fun wentToBackground() {
_background.update { true }
}
/** Indicates that the application has been brought to the foreground. */
fun inForeground() {
_background.update { false }
}
class BackgroundViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return BackgroundViewModel() as T
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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 androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update
/**
* This class is a wrapper around the [FingerprintEnrollViewModel] and decides when
* the user should or should not be enrolling.
*/
class FingerprintEnrollEnrollingViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
backgroundViewModel: BackgroundViewModel,
) : ViewModel() {
private val _didTryEnrollment = MutableStateFlow(false)
private val _userDidEnroll = MutableStateFlow(false)
/** Indicates if the enrollment flow should be running. */
val enrollFlowShouldBeRunning: Flow<Boolean> =
_userDidEnroll.combine(backgroundViewModel.background) { shouldEnroll, isInBackground ->
if (isInBackground) {
false
} else {
shouldEnroll
}
}
/**
* Used to indicate the consumer of the view model is ready for an enrollment. Note that this does
* not necessarily try an enrollment.
*/
fun canEnroll() {
// Update _consumerShouldEnroll after updating the other values.
if (!_didTryEnrollment.value) {
_didTryEnrollment.update { true }
_userDidEnroll.update { true }
}
}
/** Used to indicate to stop the enrollment. */
fun stopEnroll() {
_userDidEnroll.update { false }
}
/** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */
val enrollFLow =
enrollFlowShouldBeRunning.transformLatest {
if (it) {
fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
}
}
class FingerprintEnrollEnrollingViewModelFactory(
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val backgroundViewModel: BackgroundViewModel
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
): T {
return FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)
as T
}
}
}

View File

@@ -16,12 +16,11 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import android.hardware.fingerprint.FingerprintManager
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,6 +39,7 @@ class FingerprintEnrollFindSensorViewModel(
private val navigationViewModel: FingerprintEnrollNavigationViewModel, private val navigationViewModel: FingerprintEnrollNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel, private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
backgroundViewModel: BackgroundViewModel,
accessibilityViewModel: AccessibilityViewModel, accessibilityViewModel: AccessibilityViewModel,
foldStateViewModel: FoldStateViewModel, foldStateViewModel: FoldStateViewModel,
orientationStateViewModel: OrientationStateViewModel orientationStateViewModel: OrientationStateViewModel
@@ -88,6 +88,14 @@ class FingerprintEnrollFindSensorViewModel(
/** Represents the stream of showing error dialog. */ /** Represents the stream of showing error dialog. */
val showErrorDialog = _showErrorDialog.filterNotNull() val showErrorDialog = _showErrorDialog.filterNotNull()
private var _didTryEducation = false
private var _education: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** Indicates if the education flow should be running. */
private val educationFlowShouldBeRunning: Flow<Boolean> =
_education.combine(backgroundViewModel.background) { shouldRunEducation, isInBackground ->
!isInBackground && shouldRunEducation
}
init { init {
// Start or end enroll flow // Start or end enroll flow
viewModelScope.launch { viewModelScope.launch {
@@ -107,40 +115,58 @@ class FingerprintEnrollFindSensorViewModel(
} }
.collect { token -> .collect { token ->
if (token != null) { if (token != null) {
fingerprintEnrollViewModel.startEnroll(token, EnrollReason.FindSensor) canStartEducation()
} else { } else {
fingerprintEnrollViewModel.stopEnroll() stopEducation()
} }
} }
} }
// Enroll progress flow // Enroll progress flow
viewModelScope.launch { viewModelScope.launch {
combine( educationFlowShouldBeRunning.collect {
navigationViewModel.enrollType, // Only collect the flow when we should be running.
fingerprintEnrollViewModel.enrollFlow.filterNotNull() if (it) {
) { enrollType, enrollFlow -> combine(
Pair(enrollType, enrollFlow) navigationViewModel.fingerprintFlow,
} fingerprintEnrollViewModel.educationEnrollFlow.filterNotNull(),
.collect { (enrollType, enrollFlow) -> ) { enrollType, educationFlow ->
when (enrollFlow) { Pair(enrollType, educationFlow)
// TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding to }
// Enrolling page. Otherwise Enrolling page will receive the EnrollError. .collect { (enrollType, educationFlow) ->
is FingerEnrollStateViewModel.EnrollProgress -> proceedToEnrolling() when (educationFlow) {
is FingerEnrollStateViewModel.EnrollError -> { // TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding
val errMsgId = enrollFlow.errMsgId // to
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { // Enrolling page. Otherwise Enrolling page will receive the EnrollError.
proceedToEnrolling() is FingerEnrollState.EnrollProgress -> proceedToEnrolling()
} else { is FingerEnrollState.EnrollError -> {
_showErrorDialog.update { Pair(errMsgId, enrollType == SetupWizard) } if (educationFlow.isCancelled) {
proceedToEnrolling()
} else {
_showErrorDialog.update { Pair(educationFlow.errString, enrollType == SetupWizard) }
}
}
is FingerEnrollState.EnrollHelp -> {}
} }
} }
is FingerEnrollStateViewModel.EnrollHelp -> {}
}
} }
}
} }
} }
/** Indicates if education can begin */
private fun canStartEducation() {
if (!_didTryEducation) {
_didTryEducation = true
_education.update { true }
}
}
/** Indicates that education has finished */
private fun stopEducation() {
_education.update { false }
}
/** Proceed to EnrollEnrolling page. */ /** Proceed to EnrollEnrolling page. */
fun proceedToEnrolling() { fun proceedToEnrolling() {
navigationViewModel.nextStep() navigationViewModel.nextStep()
@@ -150,6 +176,7 @@ class FingerprintEnrollFindSensorViewModel(
private val navigationViewModel: FingerprintEnrollNavigationViewModel, private val navigationViewModel: FingerprintEnrollNavigationViewModel,
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel, private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
private val backgroundViewModel: BackgroundViewModel,
private val accessibilityViewModel: AccessibilityViewModel, private val accessibilityViewModel: AccessibilityViewModel,
private val foldStateViewModel: FoldStateViewModel, private val foldStateViewModel: FoldStateViewModel,
private val orientationStateViewModel: OrientationStateViewModel private val orientationStateViewModel: OrientationStateViewModel
@@ -160,6 +187,7 @@ class FingerprintEnrollFindSensorViewModel(
navigationViewModel, navigationViewModel,
fingerprintEnrollViewModel, fingerprintEnrollViewModel,
gatekeeperViewModel, gatekeeperViewModel,
backgroundViewModel,
accessibilityViewModel, accessibilityViewModel,
foldStateViewModel, foldStateViewModel,
orientationStateViewModel orientationStateViewModel

View File

@@ -17,32 +17,41 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine 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.shareIn
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update
private const val TAG = "FingerprintEnrollViewModel"
/** Represents all of the fingerprint information needed for a fingerprint enrollment process. */ /** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
class FingerprintEnrollViewModel( class FingerprintEnrollViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor, private val fingerprintManagerInteractor: FingerprintManagerInteractor,
backgroundDispatcher: CoroutineDispatcher, gatekeeperViewModel: FingerprintGatekeeperViewModel,
navigationViewModel: FingerprintEnrollNavigationViewModel,
) : ViewModel() { ) : ViewModel() {
private var _enrollReason: MutableStateFlow<EnrollReason> = /**
MutableStateFlow(EnrollReason.FindSensor) * Cached value of [FingerprintSensorType]
private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null) *
private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false) * This is typically used by fragments that change their layout/behavior based on this
* information. This value should be set before any fragment is created.
*/
var sensorTypeCached: FingerprintSensorType? = null
private var _enrollReason: Flow<EnrollReason?> =
navigationViewModel.navigationViewModel.map {
when (it.currStep) {
is Enrollment -> EnrollReason.EnrollEnrolling
is Education -> EnrollReason.FindSensor
else -> null
}
}
/** Represents the stream of [FingerprintSensorType] */ /** Represents the stream of [FingerprintSensorType] */
val sensorType: Flow<FingerprintSensorType> = val sensorType: Flow<FingerprintSensorType> =
@@ -51,47 +60,68 @@ class FingerprintEnrollViewModel(
/** /**
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
* an enrollment process * an enrollment process
*
* This flow should be the only flow which calls enroll().
*/ */
val enrollFlow: Flow<FingerEnrollStateViewModel> = val _enrollFlow: Flow<FingerEnrollState> =
combine(_consumerShouldEnroll, _hardwareAuthToken, _enrollReason) { combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason,
consumerShouldEnroll, ->
hardwareAuthToken, Pair(hardwareAuthToken, enrollReason)
enrollReason ->
Triple(consumerShouldEnroll, hardwareAuthToken, enrollReason)
} }
.transformLatest { .transformLatest {
// transformLatest() instead of transform() is used here for cancelling previous enroll() /** [transformLatest] is used as we want to make sure to cancel previous API call. */
// whenever |consumerShouldEnroll| is changed. Otherwise the latest value will be suspended (hardwareAuthToken, enrollReason) ->
// since enroll() is an infinite callback flow. if (hardwareAuthToken is GatekeeperInfo.GatekeeperPasswordInfo && enrollReason != null) {
(consumerShouldEnroll, hardwareAuthToken, enrollReason) -> fingerprintManagerInteractor.enroll(hardwareAuthToken.token, enrollReason).collect {
if (consumerShouldEnroll && hardwareAuthToken != null) { emit(it)
fingerprintManagerInteractor.enroll(hardwareAuthToken, enrollReason).collect { emit(it) } }
} }
} }
.flowOn(backgroundDispatcher) .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
/** Used to indicate the consumer of the view model is ready for an enrollment. */ /**
fun startEnroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) { * This flow will kick off education when
_enrollReason.update { enrollReason } * 1) There is an active subscriber to this flow
_hardwareAuthToken.update { hardwareAuthToken } * 2) shouldEnroll is true and we are on the FindSensor step
// Update _consumerShouldEnroll after updating the other values. */
_consumerShouldEnroll.update { true } val educationEnrollFlow: Flow<FingerEnrollState?> =
} _enrollReason.filterNotNull().transformLatest { enrollReason ->
if (enrollReason == EnrollReason.FindSensor) {
_enrollFlow.collect { event -> emit(event) }
} else {
emit(null)
}
}
/** Used to indicate to stop the enrollment. */ /**
fun stopEnroll() { * This flow will kick off enrollment when
_consumerShouldEnroll.update { false } * 1) There is an active subscriber to this flow
} * 2) shouldEnroll is true and we are on the EnrollEnrolling step
*/
val enrollFlow: Flow<FingerEnrollState?> =
_enrollReason.filterNotNull().transformLatest { enrollReason ->
if (enrollReason == EnrollReason.EnrollEnrolling) {
_enrollFlow.collect { event -> emit(event) }
} else {
emit(null)
}
}
class FingerprintEnrollViewModelFactory( class FingerprintEnrollViewModelFactory(
val interactor: FingerprintManagerInteractor, val interactor: FingerprintManagerInteractor,
val backgroundDispatcher: CoroutineDispatcher val gatekeeperViewModel: FingerprintGatekeeperViewModel,
val navigationViewModel: FingerprintEnrollNavigationViewModel,
) : ViewModelProvider.Factory { ) : 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, backgroundDispatcher) as T return FingerprintEnrollViewModel(
interactor,
gatekeeperViewModel,
navigationViewModel,
)
as T
} }
} }
} }

View File

@@ -21,30 +21,19 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
private const val TAG = "FingerprintEnrollNavigationViewModel" private const val TAG = "FingerprintEnrollNavigationViewModel"
/**
* The [EnrollType] for fingerprint enrollment indicates information on how the flow should behave.
*/
sealed class EnrollType
/** The default enrollment experience, typically called from Settings */
object Default : EnrollType()
/** SetupWizard/Out of box experience (OOBE) enrollment type. */
object SetupWizard : EnrollType()
/** Unicorn enrollment type */
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
@@ -53,31 +42,26 @@ class FingerprintEnrollNavigationViewModel(
private val dispatcher: CoroutineDispatcher, private val dispatcher: CoroutineDispatcher,
private val fingerprintManagerInteractor: FingerprintManagerInteractor, private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val gatekeeperViewModel: FingerprintGatekeeperViewModel, private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
private val canSkipConfirm: Boolean private val firstStep: NextStepViewModel,
private val navState: NavState,
private val theFingerprintFlow: FingerprintFlow,
) : ViewModel() { ) : ViewModel() {
private class InternalNavigationStep( private class InternalNavigationStep(
lastStep: NextStepViewModel, lastStep: NextStepViewModel,
nextStep: NextStepViewModel, nextStep: NextStepViewModel,
forward: Boolean, forward: Boolean,
var canNavigate: Boolean var canNavigate: Boolean,
) : NavigationStep(lastStep, nextStep, forward) ) : NavigationStep(lastStep, nextStep, forward)
private var _enrollType = MutableStateFlow<EnrollType?>(Default) private var _fingerprintFlow = MutableStateFlow<FingerprintFlow?>(theFingerprintFlow)
/** A flow that indicates the [EnrollType] */ /** A flow that indicates the [FingerprintFlow] */
val enrollType: Flow<EnrollType?> = _enrollType.asStateFlow() val fingerprintFlow: Flow<FingerprintFlow?> = _fingerprintFlow.asStateFlow()
private var navState = NavState(canSkipConfirm)
private val _navigationStep = private val _navigationStep =
MutableStateFlow( MutableStateFlow(
InternalNavigationStep( InternalNavigationStep(PlaceHolderState, firstStep, forward = false, canNavigate = true)
PlaceHolderState,
Start.next(navState),
forward = false,
canNavigate = true
)
) )
init { init {
@@ -96,6 +80,10 @@ class FingerprintEnrollNavigationViewModel(
*/ */
val navigationViewModel: Flow<NavigationStep> = _navigationStep.asStateFlow() val navigationViewModel: Flow<NavigationStep> = _navigationStep.asStateFlow()
/** This action indicates that the UI should actually update the navigation to the given step. */
val navigationAction: Flow<NavigationStep?> =
_navigationStep.shareIn(viewModelScope, SharingStarted.Lazily, 0)
/** Used to start the next step of Fingerprint Enrollment. */ /** Used to start the next step of Fingerprint Enrollment. */
fun nextStep() { fun nextStep() {
viewModelScope.launch { viewModelScope.launch {
@@ -130,6 +118,7 @@ class FingerprintEnrollNavigationViewModel(
private val fingerprintManagerInteractor: FingerprintManagerInteractor, private val fingerprintManagerInteractor: FingerprintManagerInteractor,
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel, private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
private val canSkipConfirm: Boolean, private val canSkipConfirm: Boolean,
private val fingerprintFlow: FingerprintFlow,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -137,11 +126,14 @@ class FingerprintEnrollNavigationViewModel(
modelClass: Class<T>, modelClass: Class<T>,
): T { ): T {
val navState = NavState(canSkipConfirm)
return FingerprintEnrollNavigationViewModel( return FingerprintEnrollNavigationViewModel(
backgroundDispatcher, backgroundDispatcher,
fingerprintManagerInteractor, fingerprintManagerInteractor,
fingerprintGatekeeperViewModel, fingerprintGatekeeperViewModel,
canSkipConfirm, Start.next(navState),
navState,
fingerprintFlow,
) )
as T as T
} }

View File

@@ -57,7 +57,7 @@ object PlaceHolderState : NextStepViewModel() {
* This state is the initial state for the current step, and will be used to determine if the user * This state is the initial state for the current step, and will be used to determine if the user
* needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro] * needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro]
*/ */
object Start : NextStepViewModel() { data object Start : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = override fun next(state: NavState): NextStepViewModel =
if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
@@ -71,19 +71,19 @@ class Finish(val resultCode: Int?) : NextStepViewModel() {
} }
/** State for the FingerprintEnrollment introduction */ /** State for the FingerprintEnrollment introduction */
object Intro : NextStepViewModel() { data object Intro : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = Education override fun next(state: NavState): NextStepViewModel = Education
override fun prev(state: NavState): NextStepViewModel = Finish(null) override fun prev(state: NavState): NextStepViewModel = Finish(null)
} }
/** State for the FingerprintEnrollment education */ /** State for the FingerprintEnrollment education */
object Education : NextStepViewModel() { data object Education : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = Enrollment override fun next(state: NavState): NextStepViewModel = Enrollment
override fun prev(state: NavState): NextStepViewModel = Intro override fun prev(state: NavState): NextStepViewModel = Intro
} }
/** State for the FingerprintEnrollment enrollment */ /** State for the FingerprintEnrollment enrollment */
object Enrollment : NextStepViewModel() { data object Enrollment : NextStepViewModel() {
override fun next(state: NavState): NextStepViewModel = Confirmation override fun next(state: NavState): NextStepViewModel = Confirmation
override fun prev(state: NavState): NextStepViewModel = Education override fun prev(state: NavState): NextStepViewModel = Education
} }

View File

@@ -19,8 +19,8 @@ package com.android.settings.biometrics.fingerprint2.ui.settings.binder
import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager
import android.util.Log import android.util.Log
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder.FingerprintView
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollAdditionalFingerprint
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
@@ -66,21 +66,21 @@ object FingerprintSettingsViewBinder {
/** Indicates what result should be set for the returning callee */ /** Indicates what result should be set for the returning callee */
fun setResultExternal(resultCode: Int) fun setResultExternal(resultCode: Int)
/** Indicates the settings UI should be shown */ /** Indicates the settings UI should be shown */
fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) fun showSettings(enrolledFingerprints: List<FingerprintData>)
/** Updates the add fingerprints preference */ /** Updates the add fingerprints preference */
fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int) fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int)
/** Updates the sfps fingerprints preference */ /** Updates the sfps fingerprints preference */
fun updateSfpsPreference(isSfpsPrefVisible: Boolean) fun updateSfpsPreference(isSfpsPrefVisible: Boolean)
/** Indicates that a user has been locked out */ /** Indicates that a user has been locked out */
fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) fun userLockout(authAttemptViewModel: FingerprintAuthAttemptModel.Error)
/** Indicates a fingerprint preference should be highlighted */ /** Indicates a fingerprint preference should be highlighted */
suspend fun highlightPref(fingerId: Int) suspend fun highlightPref(fingerId: Int)
/** Indicates a user should be prompted to delete a fingerprint */ /** Indicates a user should be prompted to delete a fingerprint */
suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintData): Boolean
/** Indicates a user should be asked to renae ma dialog */ /** Indicates a user should be asked to renae ma dialog */
suspend fun askUserToRenameDialog( suspend fun askUserToRenameDialog(
fingerprintViewModel: FingerprintViewModel fingerprintViewModel: FingerprintData
): Pair<FingerprintViewModel, String>? ): Pair<FingerprintData, String>?
} }
fun bind( fun bind(
@@ -131,10 +131,10 @@ object FingerprintSettingsViewBinder {
lifecycleScope.launch { lifecycleScope.launch {
viewModel.authFlow.filterNotNull().collect { viewModel.authFlow.filterNotNull().collect {
when (it) { when (it) {
is FingerprintAuthAttemptViewModel.Success -> { is FingerprintAuthAttemptModel.Success -> {
view.highlightPref(it.fingerId) view.highlightPref(it.fingerId)
} }
is FingerprintAuthAttemptViewModel.Error -> { is FingerprintAuthAttemptModel.Error -> {
if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) { if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
view.userLockout(it) view.userLockout(it)
} }

View File

@@ -26,7 +26,7 @@ import android.os.Bundle
import android.os.UserManager import android.os.UserManager
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.core.instrumentation.InstrumentedDialogFragment import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
@@ -34,7 +34,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
private const val KEY_IS_LAST_FINGERPRINT = "IS_LAST_FINGERPRINT" private const val KEY_IS_LAST_FINGERPRINT = "IS_LAST_FINGERPRINT"
class FingerprintDeletionDialog : InstrumentedDialogFragment() { class FingerprintDeletionDialog : InstrumentedDialogFragment() {
private lateinit var fingerprintViewModel: FingerprintViewModel private lateinit var fingerprintViewModel: FingerprintData
private var isLastFingerprint: Boolean = false private var isLastFingerprint: Boolean = false
private lateinit var alertDialog: AlertDialog private lateinit var alertDialog: AlertDialog
lateinit var onClickListener: DialogInterface.OnClickListener lateinit var onClickListener: DialogInterface.OnClickListener
@@ -51,7 +51,7 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId) fingerprintViewModel = FingerprintData(fp.name.toString(), fp.biometricId, fp.deviceId)
isLastFingerprint = requireArguments().getBoolean(KEY_IS_LAST_FINGERPRINT) isLastFingerprint = requireArguments().getBoolean(KEY_IS_LAST_FINGERPRINT)
val title = getString(R.string.fingerprint_delete_title, fingerprintViewModel.name) val title = getString(R.string.fingerprint_delete_title, fingerprintViewModel.name)
var message = getString(R.string.fingerprint_v2_delete_message, fingerprintViewModel.name) var message = getString(R.string.fingerprint_v2_delete_message, fingerprintViewModel.name)
@@ -95,9 +95,9 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
companion object { companion object {
private const val KEY_FINGERPRINT = "fingerprint" private const val KEY_FINGERPRINT = "fingerprint"
suspend fun showInstance( suspend fun showInstance(
fp: FingerprintViewModel, fp: FingerprintData,
lastFingerprint: Boolean, lastFingerprint: Boolean,
target: FingerprintSettingsV2Fragment, target: FingerprintSettingsV2Fragment,
) = suspendCancellableCoroutine { continuation -> ) = suspendCancellableCoroutine { continuation ->
val dialog = FingerprintDeletionDialog() val dialog = FingerprintDeletionDialog()
dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) } dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }

View File

@@ -22,7 +22,7 @@ import android.view.View
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settingslib.widget.TwoTargetPreference import com.android.settingslib.widget.TwoTargetPreference
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -30,10 +30,10 @@ import kotlinx.coroutines.launch
private const val TAG = "FingerprintSettingsPreference" private const val TAG = "FingerprintSettingsPreference"
class FingerprintSettingsPreference( class FingerprintSettingsPreference(
context: Context, context: Context,
val fingerprintViewModel: FingerprintViewModel, val fingerprintViewModel: FingerprintData,
val fragment: FingerprintSettingsV2Fragment, val fragment: FingerprintSettingsV2Fragment,
val isLastFingerprint: Boolean val isLastFingerprint: Boolean
) : TwoTargetPreference(context) { ) : TwoTargetPreference(context) {
private lateinit var myView: View private lateinit var myView: View
@@ -79,7 +79,7 @@ class FingerprintSettingsPreference(
return FingerprintDeletionDialog.showInstance(fingerprintViewModel, isLastFingerprint, fragment) return FingerprintDeletionDialog.showInstance(fingerprintViewModel, isLastFingerprint, fragment)
} }
suspend fun askUserToRenameDialog(): Pair<FingerprintViewModel, String>? { suspend fun askUserToRenameDialog(): Pair<FingerprintData, String>? {
return FingerprintSettingsRenameDialog.showInstance(fingerprintViewModel, fragment) return FingerprintSettingsRenameDialog.showInstance(fingerprintViewModel, fragment)
} }
} }

View File

@@ -27,7 +27,7 @@ import android.util.Log
import android.widget.ImeAwareEditText import android.widget.ImeAwareEditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.core.instrumentation.InstrumentedDialogFragment import com.android.settings.core.instrumentation.InstrumentedDialogFragment
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
@@ -46,7 +46,7 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log.d(TAG, "onCreateDialog $this") Log.d(TAG, "onCreateDialog $this")
val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
val fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId) val fingerprintViewModel = FingerprintData(fp.name.toString(), fp.biometricId, fp.deviceId)
val context = requireContext() val context = requireContext()
val alertDialog = val alertDialog =
@@ -101,7 +101,7 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
companion object { companion object {
private const val KEY_FINGERPRINT = "fingerprint" private const val KEY_FINGERPRINT = "fingerprint"
suspend fun showInstance(fp: FingerprintViewModel, target: FingerprintSettingsV2Fragment) = suspend fun showInstance(fp: FingerprintData, target: FingerprintSettingsV2Fragment) =
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
val dialog = FingerprintSettingsRenameDialog() val dialog = FingerprintSettingsRenameDialog()
val onClick = val onClick =

View File

@@ -46,8 +46,10 @@ import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
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.repository.PressToAuthProviderImpl
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.shared.model.Settings
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
@@ -142,7 +144,7 @@ class FingerprintSettingsV2Fragment :
} }
} }
override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) { override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptModel.Error) {
Toast.makeText(activity, authAttemptViewModel.message, Toast.LENGTH_SHORT).show() Toast.makeText(activity, authAttemptViewModel.message, Toast.LENGTH_SHORT).show()
} }
@@ -186,40 +188,46 @@ class FingerprintSettingsV2Fragment :
val backgroundDispatcher = Dispatchers.IO val backgroundDispatcher = Dispatchers.IO
val activity = requireActivity() val activity = requireActivity()
val userHandle = activity.user.identifier val userHandle = activity.user.identifier
// Note that SUW should not be launching FingerprintSettings
val isAnySuw = Settings
val pressToAuthProvider = {
var toReturn: Int =
Secure.getIntForUser(
context.contentResolver,
Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-1,
userHandle,
)
if (toReturn == -1) {
toReturn =
if (
context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
) {
1
} else {
0
}
Secure.putIntForUser(
context.contentResolver,
Secure.SFPS_PERFORMANT_AUTH_ENABLED,
toReturn,
userHandle
)
}
toReturn == 1
}
val interactor = val interactor =
FingerprintManagerInteractorImpl( FingerprintManagerInteractorImpl(
context.applicationContext, context.applicationContext,
backgroundDispatcher, backgroundDispatcher,
fingerprintManager, fingerprintManager,
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)) GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
) { PressToAuthProviderImpl(context),
var toReturn: Int = isAnySuw
Secure.getIntForUser( )
context.contentResolver,
Secure.SFPS_PERFORMANT_AUTH_ENABLED,
-1,
userHandle,
)
if (toReturn == -1) {
toReturn =
if (
context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
) {
1
} else {
0
}
Secure.putIntForUser(
context.contentResolver,
Secure.SFPS_PERFORMANT_AUTH_ENABLED,
toReturn,
userHandle
)
}
toReturn == 1
}
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L) val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
@@ -292,18 +300,18 @@ class FingerprintSettingsV2Fragment :
} }
/** Used to indicate that preference has been clicked */ /** Used to indicate that preference has been clicked */
fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) { fun onPrefClicked(fingerprintViewModel: FingerprintData) {
Log.d(TAG, "onPrefClicked(${fingerprintViewModel})") Log.d(TAG, "onPrefClicked(${fingerprintViewModel})")
settingsViewModel.onPrefClicked(fingerprintViewModel) settingsViewModel.onPrefClicked(fingerprintViewModel)
} }
/** Used to indicate that a delete pref has been clicked */ /** Used to indicate that a delete pref has been clicked */
fun onDeletePrefClicked(fingerprintViewModel: FingerprintViewModel) { fun onDeletePrefClicked(fingerprintViewModel: FingerprintData) {
Log.d(TAG, "onDeletePrefClicked(${fingerprintViewModel})") Log.d(TAG, "onDeletePrefClicked(${fingerprintViewModel})")
settingsViewModel.onDeleteClicked(fingerprintViewModel) settingsViewModel.onDeleteClicked(fingerprintViewModel)
} }
override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) { override fun showSettings(enrolledFingerprints: List<FingerprintData>) {
val category = val category =
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY) this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
as PreferenceCategory? as PreferenceCategory?
@@ -422,7 +430,7 @@ class FingerprintSettingsV2Fragment :
} }
} }
override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean { override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintData): Boolean {
Log.d(TAG, "showing delete dialog for (${fingerprintViewModel})") Log.d(TAG, "showing delete dialog for (${fingerprintViewModel})")
try { try {
@@ -446,8 +454,8 @@ class FingerprintSettingsV2Fragment :
} }
override suspend fun askUserToRenameDialog( override suspend fun askUserToRenameDialog(
fingerprintViewModel: FingerprintViewModel fingerprintViewModel: FingerprintData
): Pair<FingerprintViewModel, String>? { ): Pair<FingerprintData, String>? {
Log.d(TAG, "showing rename dialog for (${fingerprintViewModel})") Log.d(TAG, "showing rename dialog for (${fingerprintViewModel})")
try { try {
val toReturn = val toReturn =

View File

@@ -22,8 +22,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -53,11 +53,11 @@ class FingerprintSettingsViewModel(
private val backgroundDispatcher: CoroutineDispatcher, private val backgroundDispatcher: CoroutineDispatcher,
private val navigationViewModel: FingerprintSettingsNavigationViewModel, private val navigationViewModel: FingerprintSettingsNavigationViewModel,
) : ViewModel() { ) : ViewModel() {
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> = private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
MutableStateFlow(null) MutableStateFlow(null)
/** Represents the stream of enrolled fingerprints. */ /** Represents the stream of enrolled fingerprints. */
val enrolledFingerprints: Flow<List<FingerprintViewModel>> = val enrolledFingerprints: Flow<List<FingerprintData>> =
_enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown() _enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown()
/** Represents the stream of the information of "Add Fingerprint" preference. */ /** Represents the stream of the information of "Add Fingerprint" preference. */
@@ -95,10 +95,10 @@ class FingerprintSettingsViewModel(
private val _sensorNullOrEmpty: Flow<Boolean> = private val _sensorNullOrEmpty: Flow<Boolean> =
fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null } fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> = private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
MutableStateFlow(null) MutableStateFlow(null)
private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> = private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptModel.Success?> =
MutableSharedFlow() MutableSharedFlow()
private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0) private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
@@ -164,7 +164,7 @@ class FingerprintSettingsViewModel(
.distinctUntilChanged() .distinctUntilChanged()
/** Represents a consistent stream of authentication attempts. */ /** Represents a consistent stream of authentication attempts. */
val authFlow: Flow<FingerprintAuthAttemptViewModel> = val authFlow: Flow<FingerprintAuthAttemptModel> =
canAuthenticate canAuthenticate
.transformLatest { .transformLatest {
try { try {
@@ -173,11 +173,11 @@ class FingerprintSettingsViewModel(
Log.d(TAG, "canAuthenticate authing") Log.d(TAG, "canAuthenticate authing")
attemptingAuth() attemptingAuth()
when (val authAttempt = fingerprintManagerInteractor.authenticate()) { when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
is FingerprintAuthAttemptViewModel.Success -> { is FingerprintAuthAttemptModel.Success -> {
onAuthSuccess(authAttempt) onAuthSuccess(authAttempt)
emit(authAttempt) emit(authAttempt)
} }
is FingerprintAuthAttemptViewModel.Error -> { is FingerprintAuthAttemptModel.Error -> {
if (authAttempt.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) { if (authAttempt.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
lockout(authAttempt) lockout(authAttempt)
emit(authAttempt) emit(authAttempt)
@@ -219,7 +219,7 @@ class FingerprintSettingsViewModel(
} }
/** The fingerprint delete button has been clicked. */ /** The fingerprint delete button has been clicked. */
fun onDeleteClicked(fingerprintViewModel: FingerprintViewModel) { fun onDeleteClicked(fingerprintViewModel: FingerprintData) {
viewModelScope.launch { viewModelScope.launch {
if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) { if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
_isShowingDialog.tryEmit(PreferenceViewModel.DeleteDialog(fingerprintViewModel)) _isShowingDialog.tryEmit(PreferenceViewModel.DeleteDialog(fingerprintViewModel))
@@ -230,7 +230,7 @@ class FingerprintSettingsViewModel(
} }
/** The rename fingerprint dialog has been clicked. */ /** The rename fingerprint dialog has been clicked. */
fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) { fun onPrefClicked(fingerprintViewModel: FingerprintData) {
viewModelScope.launch { viewModelScope.launch {
if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) { if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
_isShowingDialog.tryEmit(PreferenceViewModel.RenameDialog(fingerprintViewModel)) _isShowingDialog.tryEmit(PreferenceViewModel.RenameDialog(fingerprintViewModel))
@@ -241,7 +241,7 @@ class FingerprintSettingsViewModel(
} }
/** A request to delete a fingerprint */ /** A request to delete a fingerprint */
fun deleteFingerprint(fp: FingerprintViewModel) { fun deleteFingerprint(fp: FingerprintData) {
viewModelScope.launch(backgroundDispatcher) { viewModelScope.launch(backgroundDispatcher) {
if (fingerprintManagerInteractor.removeFingerprint(fp)) { if (fingerprintManagerInteractor.removeFingerprint(fp)) {
updateEnrolledFingerprints() updateEnrolledFingerprints()
@@ -250,7 +250,7 @@ class FingerprintSettingsViewModel(
} }
/** A request to rename a fingerprint */ /** A request to rename a fingerprint */
fun renameFingerprint(fp: FingerprintViewModel, newName: String) { fun renameFingerprint(fp: FingerprintData, newName: String) {
viewModelScope.launch { viewModelScope.launch {
fingerprintManagerInteractor.renameFingerprint(fp, newName) fingerprintManagerInteractor.renameFingerprint(fp, newName)
updateEnrolledFingerprints() updateEnrolledFingerprints()
@@ -261,12 +261,12 @@ class FingerprintSettingsViewModel(
_attemptsSoFar.update { it + 1 } _attemptsSoFar.update { it + 1 }
} }
private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) { private suspend fun onAuthSuccess(success: FingerprintAuthAttemptModel.Success) {
_authSucceeded.emit(success) _authSucceeded.emit(success)
_attemptsSoFar.update { 0 } _attemptsSoFar.update { 0 }
} }
private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) { private fun lockout(attemptViewModel: FingerprintAuthAttemptModel.Error) {
_isLockedOut.update { attemptViewModel } _isLockedOut.update { attemptViewModel }
} }

View File

@@ -16,15 +16,15 @@
package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel package com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
/** Classed use to represent a Dialogs state. */ /** Classed use to represent a Dialogs state. */
sealed class PreferenceViewModel { sealed class PreferenceViewModel {
data class RenameDialog( data class RenameDialog(
val fingerprintViewModel: FingerprintViewModel, val fingerprintViewModel: FingerprintData,
) : PreferenceViewModel() ) : PreferenceViewModel()
data class DeleteDialog( data class DeleteDialog(
val fingerprintViewModel: FingerprintViewModel, val fingerprintViewModel: FingerprintData,
) : PreferenceViewModel() ) : PreferenceViewModel()
} }

View File

@@ -33,12 +33,15 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel 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.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.GatekeeperInfo import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NavState
import com.android.settings.testutils2.FakeFingerprintManagerInteractor import com.android.settings.testutils2.FakeFingerprintManagerInteractor
import com.google.android.setupdesign.GlifLayout import com.google.android.setupdesign.GlifLayout
import com.google.android.setupdesign.template.RequireScrollMixin import com.google.android.setupdesign.template.RequireScrollMixin
@@ -65,9 +68,12 @@ class FingerprintEnrollIntroFragmentTest {
backgroundDispatcher, backgroundDispatcher,
interactor, interactor,
gatekeeperViewModel, gatekeeperViewModel,
canSkipConfirm = true, Intro,
NavState(true),
Default,
) )
private var fingerprintViewModel = FingerprintEnrollViewModel(interactor, backgroundDispatcher) private var fingerprintViewModel =
FingerprintEnrollViewModel(interactor, gatekeeperViewModel, navigationViewModel)
private var fingerprintScrollViewModel = FingerprintScrollViewModel() private var fingerprintScrollViewModel = FingerprintScrollViewModel()
@Before @Before

View File

@@ -18,9 +18,9 @@ package com.android.settings.testutils2
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.systemui.biometrics.shared.model.FingerprintSensor import com.android.systemui.biometrics.shared.model.FingerprintSensor
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.biometrics.shared.model.SensorStrength
@@ -32,10 +32,11 @@ import kotlinx.coroutines.flow.flowOf
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor { class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
var enrollableFingerprints: Int = 5 var enrollableFingerprints: Int = 5
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf() var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf()) var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1) var authenticateAttempt = FingerprintAuthAttemptModel.Success(1)
val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1) var enrollStateViewModel: List<FingerEnrollState> =
listOf(FingerEnrollState.EnrollProgress(5, 5))
var pressToAuthEnabled = true var pressToAuthEnabled = true
var sensorProp = var sensorProp =
@@ -46,7 +47,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
FingerprintSensorType.POWER_BUTTON FingerprintSensorType.POWER_BUTTON
) )
override suspend fun authenticate(): FingerprintAuthAttemptViewModel { override suspend fun authenticate(): FingerprintAuthAttemptModel {
return authenticateAttempt return authenticateAttempt
} }
@@ -54,7 +55,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
return challengeToGenerate return challengeToGenerate
} }
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow { override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
emit(enrolledFingerprintsInternal) emit(enrolledFingerprintsInternal)
} }
@@ -62,24 +63,22 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
emit(enrolledFingerprintsInternal.size < enrollableFingerprints) emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
} }
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
emit(sensorProp)
}
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) } override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
override suspend fun enroll( override suspend fun enroll(
hardwareAuthToken: ByteArray?, hardwareAuthToken: ByteArray?,
enrollReason: EnrollReason enrollReason: EnrollReason
): Flow<FingerEnrollStateViewModel> = flowOf(enrollStateViewModel) ): Flow<FingerEnrollState> = flowOf(*enrollStateViewModel.toTypedArray())
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean { override suspend fun removeFingerprint(fp: FingerprintData): Boolean {
return enrolledFingerprintsInternal.remove(fp) return enrolledFingerprintsInternal.remove(fp)
} }
override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) { override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
if (enrolledFingerprintsInternal.remove(fp)) { if (enrolledFingerprintsInternal.remove(fp)) {
enrolledFingerprintsInternal.add(FingerprintViewModel(newName, fp.fingerId, fp.deviceId)) enrolledFingerprintsInternal.add(FingerprintData(newName, fp.fingerId, fp.deviceId))
} }
} }

View File

@@ -26,12 +26,14 @@ import android.os.CancellationSignal
import android.os.Handler import android.os.Handler
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
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.data.repository.PressToAuthProvider
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
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
@@ -69,7 +71,11 @@ class FingerprintManagerInteractorTest {
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
private var testScope = TestScope(backgroundDispatcher) private var testScope = TestScope(backgroundDispatcher)
private var pressToAuthProvider = { true } private var pressToAuthProvider =
object : PressToAuthProvider {
override val isEnabled: Boolean
get() = false
}
@Before @Before
fun setup() { fun setup() {
@@ -80,6 +86,7 @@ class FingerprintManagerInteractorTest {
fingerprintManager, fingerprintManager,
gateKeeperPasswordProvider, gateKeeperPasswordProvider,
pressToAuthProvider, pressToAuthProvider,
Default,
) )
} }
@@ -164,7 +171,7 @@ class FingerprintManagerInteractorTest {
@Test @Test
fun testRemoveFingerprint_succeeds() = fun testRemoveFingerprint_succeeds() =
testScope.runTest { testScope.runTest {
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L) val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L) val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor() val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
@@ -187,7 +194,7 @@ class FingerprintManagerInteractorTest {
@Test @Test
fun testRemoveFingerprint_fails() = fun testRemoveFingerprint_fails() =
testScope.runTest { testScope.runTest {
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L) val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L) val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor() val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
@@ -214,7 +221,7 @@ class FingerprintManagerInteractorTest {
@Test @Test
fun testRenameFingerprint_succeeds() = fun testRenameFingerprint_succeeds() =
testScope.runTest { testScope.runTest {
val fingerprintToRename = FingerprintViewModel("Finger 2", 1, 2L) val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
underTest.renameFingerprint(fingerprintToRename, "Woo") underTest.renameFingerprint(fingerprintToRename, "Woo")
@@ -226,7 +233,7 @@ class FingerprintManagerInteractorTest {
testScope.runTest { testScope.runTest {
val fingerprint = Fingerprint("Woooo", 100, 101L) val fingerprint = Fingerprint("Woooo", 100, 101L)
var result: FingerprintAuthAttemptViewModel? = null var result: FingerprintAuthAttemptModel? = null
val job = launch { result = underTest.authenticate() } val job = launch { result = underTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor() val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -247,13 +254,13 @@ class FingerprintManagerInteractorTest {
runCurrent() runCurrent()
job.cancelAndJoin() job.cancelAndJoin()
assertThat(result).isEqualTo(FingerprintAuthAttemptViewModel.Success(fingerprint.biometricId)) assertThat(result).isEqualTo(FingerprintAuthAttemptModel.Success(fingerprint.biometricId))
} }
@Test @Test
fun testAuth_lockout() = fun testAuth_lockout() =
testScope.runTest { testScope.runTest {
var result: FingerprintAuthAttemptViewModel? = null var result: FingerprintAuthAttemptModel? = null
val job = launch { result = underTest.authenticate() } val job = launch { result = underTest.authenticate() }
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor() val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
@@ -274,7 +281,7 @@ class FingerprintManagerInteractorTest {
job.cancelAndJoin() job.cancelAndJoin()
assertThat(result) assertThat(result)
.isEqualTo( .isEqualTo(
FingerprintAuthAttemptViewModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!") FingerprintAuthAttemptModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
) )
} }
@@ -282,7 +289,7 @@ class FingerprintManagerInteractorTest {
fun testEnroll_progress() = fun testEnroll_progress() =
testScope.runTest { testScope.runTest {
val token = byteArrayOf(5, 3, 2) val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null var result: FingerEnrollState? = null
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } } val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor() val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent() runCurrent()
@@ -299,14 +306,14 @@ class FingerprintManagerInteractorTest {
runCurrent() runCurrent()
job.cancelAndJoin() job.cancelAndJoin()
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollProgress(1)) assertThat(result).isEqualTo(FingerEnrollState.EnrollProgress(1, 2))
} }
@Test @Test
fun testEnroll_help() = fun testEnroll_help() =
testScope.runTest { testScope.runTest {
val token = byteArrayOf(5, 3, 2) val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null var result: FingerEnrollState? = null
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } } val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor() val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent() runCurrent()
@@ -323,14 +330,14 @@ class FingerprintManagerInteractorTest {
runCurrent() runCurrent()
job.cancelAndJoin() job.cancelAndJoin()
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollHelp(-1, "help")) assertThat(result).isEqualTo(FingerEnrollState.EnrollHelp(-1, "help"))
} }
@Test @Test
fun testEnroll_error() = fun testEnroll_error() =
testScope.runTest { testScope.runTest {
val token = byteArrayOf(5, 3, 2) val token = byteArrayOf(5, 3, 2)
var result: FingerEnrollStateViewModel? = null var result: FingerEnrollState? = null
val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } } val job = launch { underTest.enroll(token, EnrollReason.FindSensor).collect { result = it } }
val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor() val enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
runCurrent() runCurrent()
@@ -343,17 +350,20 @@ class FingerprintManagerInteractorTest {
capture(enrollCallback), capture(enrollCallback),
eq(FingerprintManager.ENROLL_FIND_SENSOR) eq(FingerprintManager.ENROLL_FIND_SENSOR)
) )
enrollCallback.value.onEnrollmentError(-2, "error") enrollCallback.value.onEnrollmentError(-1, "error")
runCurrent() runCurrent()
job.cancelAndJoin() job.cancelAndJoin()
assertThat(result).isInstanceOf(FingerEnrollState.EnrollError::class.java)
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> = inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
ArgumentCaptor.forClass(T::class.java) ArgumentCaptor.forClass(T::class.java)
} }

View File

@@ -21,7 +21,9 @@ import android.content.res.Configuration
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
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.FingerprintEnrollFindSensorViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
@@ -70,6 +72,7 @@ class FingerprintEnrollFindSensorViewModelV2Test {
private lateinit var foldStateViewModel: FoldStateViewModel private lateinit var foldStateViewModel: FoldStateViewModel
private lateinit var orientationStateViewModel: OrientationStateViewModel private lateinit var orientationStateViewModel: OrientationStateViewModel
private lateinit var underTest: FingerprintEnrollFindSensorViewModel private lateinit var underTest: FingerprintEnrollFindSensorViewModel
private lateinit var backgroundViewModel: BackgroundViewModel
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context = ApplicationProvider.getApplicationContext()
private val accessibilityManager: AccessibilityManager = private val accessibilityManager: AccessibilityManager =
context.getSystemService(AccessibilityManager::class.java)!! context.getSystemService(AccessibilityManager::class.java)!!
@@ -93,12 +96,18 @@ class FingerprintEnrollFindSensorViewModelV2Test {
fakeFingerprintManagerInteractor, fakeFingerprintManagerInteractor,
gatekeeperViewModel, gatekeeperViewModel,
canSkipConfirm = true, canSkipConfirm = true,
Default,
) )
.create(FingerprintEnrollNavigationViewModel::class.java) .create(FingerprintEnrollNavigationViewModel::class.java)
backgroundViewModel =
BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
backgroundViewModel.inForeground()
enrollViewModel = enrollViewModel =
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory( FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
fakeFingerprintManagerInteractor, fakeFingerprintManagerInteractor,
backgroundDispatcher gatekeeperViewModel,
navigationViewModel,
) )
.create(FingerprintEnrollViewModel::class.java) .create(FingerprintEnrollViewModel::class.java)
accessibilityViewModel = accessibilityViewModel =
@@ -114,6 +123,7 @@ class FingerprintEnrollFindSensorViewModelV2Test {
navigationViewModel, navigationViewModel,
enrollViewModel, enrollViewModel,
gatekeeperViewModel, gatekeeperViewModel,
backgroundViewModel,
accessibilityViewModel, accessibilityViewModel,
foldStateViewModel, foldStateViewModel,
orientationStateViewModel orientationStateViewModel
@@ -123,6 +133,7 @@ class FingerprintEnrollFindSensorViewModelV2Test {
// Navigate to Education page // Navigate to Education page
navigationViewModel.nextStep() navigationViewModel.nextStep()
} }
@After @After
fun tearDown() { fun tearDown() {
Dispatchers.resetMain() Dispatchers.resetMain()

View File

@@ -0,0 +1,142 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fingerprint2.ui.enrollment.modules.enrolling.rfps.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class RFPSIconTouchViewModelTest {
@JvmField @Rule var rule = MockitoJUnit.rule()
@get:Rule val instantTaskRule = InstantTaskExecutorRule()
private var backgroundDispatcher = StandardTestDispatcher()
private var testScope = TestScope(backgroundDispatcher)
private lateinit var rfpsIconTouchViewModel: RFPSIconTouchViewModel
@Before
fun setup() {
Dispatchers.setMain(backgroundDispatcher)
testScope = TestScope(backgroundDispatcher)
rfpsIconTouchViewModel =
RFPSIconTouchViewModel()
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun initShouldNotShowDialog() =
testScope.runTest {
var shouldShowDialog = false
val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
runCurrent()
assertThat(shouldShowDialog).isFalse()
job.cancel()
}
@Test
fun shouldShowDialogTest() =
testScope.runTest {
var shouldShowDialog = false
val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
runCurrent()
assertThat(shouldShowDialog).isTrue()
job.cancel()
}
@Test
fun stateShouldBeFalseAfterReset() =
testScope.runTest {
var shouldShowDialog = false
val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
runCurrent()
assertThat(shouldShowDialog).isTrue()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
runCurrent()
assertThat(shouldShowDialog).isFalse()
job.cancel()
}
@Test
fun toggleMultipleTimes() =
testScope.runTest {
var shouldShowDialog = false
val job = launch { rfpsIconTouchViewModel.shouldShowDialog.collect { shouldShowDialog = it } }
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
runCurrent()
assertThat(shouldShowDialog).isTrue()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
runCurrent()
assertThat(shouldShowDialog).isFalse()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
rfpsIconTouchViewModel.userTouchedFingerprintIcon()
runCurrent()
assertThat(shouldShowDialog).isTrue()
job.cancel()
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fingerprint2.ui.enrollment.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.fingerprint2.shared.model.Default
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
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.FingerprintGatekeeperViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.NavState
import com.android.settings.testutils2.FakeFingerprintManagerInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class FingerprintEnrollEnrollingViewModelTest {
@JvmField @Rule var rule = MockitoJUnit.rule()
@get:Rule val instantTaskRule = InstantTaskExecutorRule()
private var backgroundDispatcher = StandardTestDispatcher()
private lateinit var enrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
private lateinit var backgroundViewModel: BackgroundViewModel
private lateinit var gateKeeperViewModel: FingerprintGatekeeperViewModel
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
private val defaultGatekeeperInfo = GatekeeperInfo.GatekeeperPasswordInfo(byteArrayOf(1, 3), 3)
private var testScope = TestScope(backgroundDispatcher)
private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
private fun initialize(gatekeeperInfo: GatekeeperInfo = defaultGatekeeperInfo) {
fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
gateKeeperViewModel =
FingerprintGatekeeperViewModel.FingerprintGatekeeperViewModelFactory(
gatekeeperInfo,
fakeFingerprintManagerInteractor
)
.create(FingerprintGatekeeperViewModel::class.java)
navigationViewModel =
FingerprintEnrollNavigationViewModel(
backgroundDispatcher,
fakeFingerprintManagerInteractor,
gateKeeperViewModel,
Enrollment,
NavState(true),
Default,
)
backgroundViewModel =
BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
backgroundViewModel.inForeground()
val fingerprintEnrollViewModel =
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
fakeFingerprintManagerInteractor,
gateKeeperViewModel,
navigationViewModel,
)
.create(FingerprintEnrollViewModel::class.java)
enrollEnrollingViewModel =
FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
fingerprintEnrollViewModel,
backgroundViewModel,
)
.create(FingerprintEnrollEnrollingViewModel::class.java)
}
@Before
fun setup() {
Dispatchers.setMain(backgroundDispatcher)
initialize()
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun testEnrollShouldBeFalse() =
testScope.runTest {
var shouldEnroll = false
val job = launch {
enrollEnrollingViewModel.enrollFlowShouldBeRunning.collect { shouldEnroll = it }
}
assertThat(shouldEnroll).isFalse()
runCurrent()
enrollEnrollingViewModel.canEnroll()
runCurrent()
assertThat(shouldEnroll).isTrue()
job.cancel()
}
@Test
fun testEnrollShouldBeFalseWhenBackground() =
testScope.runTest {
var shouldEnroll = false
val job = launch {
enrollEnrollingViewModel.enrollFlowShouldBeRunning.collect { shouldEnroll = it }
}
assertThat(shouldEnroll).isFalse()
runCurrent()
enrollEnrollingViewModel.canEnroll()
runCurrent()
assertThat(shouldEnroll).isTrue()
backgroundViewModel.wentToBackground()
runCurrent()
assertThat(shouldEnroll).isFalse()
job.cancel()
}
}

View File

@@ -18,7 +18,7 @@ package com.android.settings.fingerprint2.ui.settings
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.BiometricEnrollBase import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.EnrollFirstFingerprint
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FinishSettings import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FinishSettings
@@ -208,7 +208,7 @@ class FingerprintSettingsNavigationViewModelTest {
fun enrollAdditionalFingerprints_fails() = fun enrollAdditionalFingerprints_fails() =
testScope.runTest { testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1)) fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1))
var nextStep: NextStepViewModel? = null var nextStep: NextStepViewModel? = null
@@ -227,7 +227,7 @@ class FingerprintSettingsNavigationViewModelTest {
fun enrollAdditional_success() = fun enrollAdditional_success() =
testScope.runTest { testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
var nextStep: NextStepViewModel? = null var nextStep: NextStepViewModel? = null
val job = launch { underTest.nextStep.collect { nextStep = it } } val job = launch { underTest.nextStep.collect { nextStep = it } }
@@ -245,7 +245,7 @@ class FingerprintSettingsNavigationViewModelTest {
fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() = fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() =
testScope.runTest { testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3)) fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3))
var nextStep: NextStepViewModel? = null var nextStep: NextStepViewModel? = null
@@ -320,7 +320,7 @@ class FingerprintSettingsNavigationViewModelTest {
fun showSettings_shouldFinish() = fun showSettings_shouldFinish() =
testScope.runTest { testScope.runTest {
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
var nextStep: NextStepViewModel? = null var nextStep: NextStepViewModel? = null
val job = launch { underTest.nextStep.collect { nextStep = it } } val job = launch { underTest.nextStep.collect { nextStep = it } }

View File

@@ -17,8 +17,8 @@
package com.android.settings.fingerprint2.ui.settings package com.android.settings.fingerprint2.ui.settings
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.PreferenceViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.PreferenceViewModel
@@ -103,7 +103,7 @@ class FingerprintSettingsViewModelTest {
FingerprintSensorType.UDFPS_OPTICAL, FingerprintSensorType.UDFPS_OPTICAL,
) )
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
underTest = underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -114,7 +114,7 @@ class FingerprintSettingsViewModelTest {
) )
.create(FingerprintSettingsViewModel::class.java) .create(FingerprintSettingsViewModel::class.java)
var authAttempt: FingerprintAuthAttemptViewModel? = null var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } } val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true) underTest.shouldAuthenticate(true)
@@ -139,7 +139,7 @@ class FingerprintSettingsViewModelTest {
FingerprintSensorType.UDFPS_ULTRASONIC, FingerprintSensorType.UDFPS_ULTRASONIC,
) )
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
underTest = underTest =
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory( FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
@@ -150,7 +150,7 @@ class FingerprintSettingsViewModelTest {
) )
.create(FingerprintSettingsViewModel::class.java) .create(FingerprintSettingsViewModel::class.java)
var authAttempt: FingerprintAuthAttemptViewModel? = null var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } } val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true) underTest.shouldAuthenticate(true)
@@ -173,8 +173,8 @@ class FingerprintSettingsViewModelTest {
FingerprintSensorType.POWER_BUTTON FingerprintSensorType.POWER_BUTTON
) )
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(FingerprintViewModel("a", 1, 3L)) mutableListOf(FingerprintData("a", 1, 3L))
val success = FingerprintAuthAttemptViewModel.Success(1) val success = FingerprintAuthAttemptModel.Success(1)
fakeFingerprintManagerInteractor.authenticateAttempt = success fakeFingerprintManagerInteractor.authenticateAttempt = success
underTest = underTest =
@@ -186,7 +186,7 @@ class FingerprintSettingsViewModelTest {
) )
.create(FingerprintSettingsViewModel::class.java) .create(FingerprintSettingsViewModel::class.java)
var authAttempt: FingerprintAuthAttemptViewModel? = null var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } } val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true) underTest.shouldAuthenticate(true)
@@ -200,7 +200,7 @@ class FingerprintSettingsViewModelTest {
@Test @Test
fun deleteDialog_showAndDismiss() = runTest { fun deleteDialog_showAndDismiss() = runTest {
val fingerprintToDelete = FingerprintViewModel("A", 1, 10L) val fingerprintToDelete = FingerprintData("A", 1, 10L)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToDelete) mutableListOf(fingerprintToDelete)
@@ -236,7 +236,7 @@ class FingerprintSettingsViewModelTest {
@Test @Test
fun renameDialog_showAndDismiss() = runTest { fun renameDialog_showAndDismiss() = runTest {
val fingerprintToRename = FingerprintViewModel("World", 1, 10L) val fingerprintToRename = FingerprintData("World", 1, 10L)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToRename) mutableListOf(fingerprintToRename)
@@ -274,7 +274,7 @@ class FingerprintSettingsViewModelTest {
@Test @Test
fun testTwoDialogsCannotShow_atSameTime() = runTest { fun testTwoDialogsCannotShow_atSameTime() = runTest {
val fingerprintToDelete = FingerprintViewModel("A", 1, 10L) val fingerprintToDelete = FingerprintData("A", 1, 10L)
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
mutableListOf(fingerprintToDelete) mutableListOf(fingerprintToDelete)
@@ -311,9 +311,9 @@ class FingerprintSettingsViewModelTest {
fun authenticatePauses_whenPaused() = fun authenticatePauses_whenPaused() =
testScope.runTest { testScope.runTest {
val fingerprints = setupAuth() val fingerprints = setupAuth()
val success = FingerprintAuthAttemptViewModel.Success(fingerprints.first().fingerId) val success = FingerprintAuthAttemptModel.Success(fingerprints.first().fingerId)
var authAttempt: FingerprintAuthAttemptViewModel? = null var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } } val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
@@ -325,7 +325,7 @@ class FingerprintSettingsViewModelTest {
assertThat(authAttempt).isEqualTo(success) assertThat(authAttempt).isEqualTo(success)
fakeFingerprintManagerInteractor.authenticateAttempt = fakeFingerprintManagerInteractor.authenticateAttempt =
FingerprintAuthAttemptViewModel.Success(10) FingerprintAuthAttemptModel.Success(10)
underTest.shouldAuthenticate(false) underTest.shouldAuthenticate(false)
advanceTimeBy(400) advanceTimeBy(400)
runCurrent() runCurrent()
@@ -340,7 +340,7 @@ class FingerprintSettingsViewModelTest {
testScope.runTest { testScope.runTest {
val fingerprints = setupAuth() val fingerprints = setupAuth()
var authAttempt: FingerprintAuthAttemptViewModel? = null var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } } val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true) underTest.shouldAuthenticate(true)
navigationViewModel.onConfirmDevice(true, 10L) navigationViewModel.onConfirmDevice(true, 10L)
@@ -357,7 +357,7 @@ class FingerprintSettingsViewModelTest {
testScope.runTest { testScope.runTest {
val fingerprints = setupAuth() val fingerprints = setupAuth()
var authAttempt: FingerprintAuthAttemptViewModel? = null var authAttempt: FingerprintAuthAttemptModel? = null
val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } } val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
underTest.shouldAuthenticate(true) underTest.shouldAuthenticate(true)
navigationViewModel.onConfirmDevice(true, 10L) navigationViewModel.onConfirmDevice(true, 10L)
@@ -370,7 +370,7 @@ class FingerprintSettingsViewModelTest {
assertThat(authAttempt).isEqualTo(null) assertThat(authAttempt).isEqualTo(null)
} }
private fun setupAuth(): MutableList<FingerprintViewModel> { private fun setupAuth(): MutableList<FingerprintData> {
fakeFingerprintManagerInteractor.sensorProp = fakeFingerprintManagerInteractor.sensorProp =
FingerprintSensor( FingerprintSensor(
0 /* sensorId */, 0 /* sensorId */,
@@ -379,9 +379,9 @@ class FingerprintSettingsViewModelTest {
FingerprintSensorType.POWER_BUTTON FingerprintSensorType.POWER_BUTTON
) )
val fingerprints = val fingerprints =
mutableListOf(FingerprintViewModel("a", 1, 3L), FingerprintViewModel("b", 2, 5L)) mutableListOf(FingerprintData("a", 1, 3L), FingerprintData("b", 2, 5L))
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fingerprints fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fingerprints
val success = FingerprintAuthAttemptViewModel.Success(1) val success = FingerprintAuthAttemptModel.Success(1)
fakeFingerprintManagerInteractor.authenticateAttempt = success fakeFingerprintManagerInteractor.authenticateAttempt = success
underTest = underTest =