Rear Fingerprint Enrollment
Bug: 297083009 Test: atest RFPSIconTouchViewModelTest FingerprintEnrollEnrollingViewModelTest FingerprintManagerInteractorTest Change-Id: Icc072e7d7815070087ccb50ea5937c386b06fb11
This commit is contained in:
75
res/layout/fingerprint_v2_rfps_enroll_enrolling.xml
Normal file
75
res/layout/fingerprint_v2_rfps_enroll_enrolling.xml
Normal 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>
|
@@ -16,14 +16,61 @@
|
||||
|
||||
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 com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
|
||||
|
||||
class Util
|
||||
|
||||
fun EnrollReason.toOriginalReason(): Int {
|
||||
return when (this) {
|
||||
EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
|
||||
EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
|
||||
object Util {
|
||||
fun EnrollReason.toOriginalReason(): Int {
|
||||
return when (this) {
|
||||
EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -24,12 +24,16 @@ import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
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.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
|
||||
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.systemui.biometrics.shared.model.toFingerprintSensor
|
||||
import kotlin.coroutines.resume
|
||||
@@ -38,9 +42,12 @@ import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -51,7 +58,8 @@ class FingerprintManagerInteractorImpl(
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
|
||||
private val pressToAuthProvider: () -> Boolean,
|
||||
private val pressToAuthProvider: PressToAuthProvider,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
) : FingerprintManagerInteractor {
|
||||
|
||||
private val maxFingerprints =
|
||||
@@ -60,6 +68,8 @@ class FingerprintManagerInteractorImpl(
|
||||
)
|
||||
private val applicationContext = applicationContext.applicationContext
|
||||
|
||||
private val enrollRequestOutstanding = MutableStateFlow(false)
|
||||
|
||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
|
||||
suspendCoroutine {
|
||||
val callback = GenerateChallengeCallback { _, userId, challenge ->
|
||||
@@ -75,11 +85,11 @@ class FingerprintManagerInteractorImpl(
|
||||
fingerprintManager.generateChallenge(applicationContext.userId, callback)
|
||||
}
|
||||
|
||||
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
|
||||
override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
|
||||
emit(
|
||||
fingerprintManager
|
||||
.getEnrolledFingerprints(applicationContext.userId)
|
||||
.map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) }
|
||||
.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
|
||||
.toList()
|
||||
)
|
||||
}
|
||||
@@ -103,28 +113,51 @@ class FingerprintManagerInteractorImpl(
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
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 totalSteps: Int? = null
|
||||
val enrollmentCallback =
|
||||
object : FingerprintManager.EnrollmentCallback() {
|
||||
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")
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
streamEnded = true
|
||||
enrollRequestOutstanding.update { false }
|
||||
}
|
||||
}
|
||||
|
||||
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") }
|
||||
}
|
||||
|
||||
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") }
|
||||
Log.d(TAG, "onEnrollmentError($errMsgId)")
|
||||
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
|
||||
// before it was over, send cancel.
|
||||
if (!streamEnded) {
|
||||
Log.e(TAG, "Cancel is sent from settings for enroll()")
|
||||
cancellationSignal.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
|
||||
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
|
||||
val callback =
|
||||
object : RemovalCallback() {
|
||||
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) {
|
||||
fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
|
||||
}
|
||||
@@ -181,11 +215,11 @@ class FingerprintManagerInteractorImpl(
|
||||
}
|
||||
|
||||
override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
|
||||
it.resume(pressToAuthProvider())
|
||||
it.resume(pressToAuthProvider.isEnabled)
|
||||
}
|
||||
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
|
||||
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptModel =
|
||||
suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
|
||||
val authenticationCallback =
|
||||
object : FingerprintManager.AuthenticationCallback() {
|
||||
|
||||
@@ -195,7 +229,7 @@ class FingerprintManagerInteractorImpl(
|
||||
Log.d(TAG, "framework sent down onAuthError after finish")
|
||||
return
|
||||
}
|
||||
c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString()))
|
||||
c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
|
||||
@@ -204,7 +238,7 @@ class FingerprintManagerInteractorImpl(
|
||||
Log.d(TAG, "framework sent down onAuthError after finish")
|
||||
return
|
||||
}
|
||||
c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1))
|
||||
c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -17,9 +17,9 @@
|
||||
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.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
|
||||
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.FingerEnrollState
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
*/
|
||||
interface FingerprintManagerInteractor {
|
||||
/** 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 */
|
||||
val maxEnrollableFingerprints: Flow<Int>
|
||||
@@ -43,7 +43,7 @@ interface FingerprintManagerInteractor {
|
||||
val sensorPropertiesInternal: Flow<FingerprintSensor?>
|
||||
|
||||
/** Runs the authenticate flow */
|
||||
suspend fun authenticate(): FingerprintAuthAttemptViewModel
|
||||
suspend fun authenticate(): FingerprintAuthAttemptModel
|
||||
|
||||
/**
|
||||
* 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
|
||||
* enrollment. Returning the [FingerEnrollStateViewModel] that represents this fingerprint
|
||||
* enrollment. Returning the [FingerEnrollState] that represents this fingerprint
|
||||
* enrollment state.
|
||||
*/
|
||||
suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
): Flow<FingerEnrollStateViewModel>
|
||||
hardwareAuthToken: ByteArray?,
|
||||
enrollReason: EnrollReason,
|
||||
): Flow<FingerEnrollState>
|
||||
|
||||
/**
|
||||
* Removes the given fingerprint, returning true if it was successfully removed and false
|
||||
* otherwise
|
||||
*/
|
||||
suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
|
||||
suspend fun removeFingerprint(fp: FingerprintData): Boolean
|
||||
|
||||
/** 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 */
|
||||
suspend fun hasSideFps(): Boolean
|
||||
|
@@ -22,19 +22,28 @@ import android.annotation.StringRes
|
||||
* Represents a fingerprint enrollment state. See [FingerprintManager.EnrollmentCallback] for more
|
||||
* information
|
||||
*/
|
||||
sealed class FingerEnrollStateViewModel {
|
||||
/** Represents enrollment step progress. */
|
||||
sealed class FingerEnrollState {
|
||||
/**
|
||||
* Represents an enrollment step progress.
|
||||
*
|
||||
* Progress is obtained by (totalStepsRequired - remainingSteps) / totalStepsRequired
|
||||
*/
|
||||
data class EnrollProgress(
|
||||
val remainingSteps: Int,
|
||||
) : FingerEnrollStateViewModel()
|
||||
val totalStepsRequired: Int,
|
||||
) : FingerEnrollState()
|
||||
|
||||
/** Represents that recoverable error has been encountered during enrollment. */
|
||||
data class EnrollHelp(
|
||||
@StringRes val helpMsgId: Int,
|
||||
val helpString: String,
|
||||
) : FingerEnrollStateViewModel()
|
||||
) : FingerEnrollState()
|
||||
|
||||
/** Represents that an unrecoverable error has been encountered and the operation is complete. */
|
||||
data class EnrollError(
|
||||
@StringRes val errMsgId: Int,
|
||||
val errString: String,
|
||||
) : FingerEnrollStateViewModel()
|
||||
@StringRes val errTitle: Int,
|
||||
@StringRes val errString: Int,
|
||||
val shouldRetryEnrollment: Boolean,
|
||||
val isCancelled: Boolean,
|
||||
) : FingerEnrollState()
|
||||
}
|
@@ -16,19 +16,19 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.shared.model
|
||||
|
||||
data class FingerprintViewModel(
|
||||
data class FingerprintData(
|
||||
val name: String,
|
||||
val fingerId: Int,
|
||||
val deviceId: Long,
|
||||
)
|
||||
|
||||
sealed class FingerprintAuthAttemptViewModel {
|
||||
sealed class FingerprintAuthAttemptModel {
|
||||
data class Success(
|
||||
val fingerId: Int,
|
||||
) : FingerprintAuthAttemptViewModel()
|
||||
) : FingerprintAuthAttemptModel()
|
||||
|
||||
data class Error(
|
||||
val error: Int,
|
||||
val message: String,
|
||||
) : FingerprintAuthAttemptViewModel()
|
||||
) : FingerprintAuthAttemptModel()
|
||||
}
|
@@ -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()
|
@@ -16,12 +16,9 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity
|
||||
|
||||
import android.annotation.ColorInt
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
@@ -35,22 +32,27 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.settings.R
|
||||
import com.android.settings.SetupWizardUtils
|
||||
import com.android.settings.Utils
|
||||
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
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.FingerprintEnrollEnrollingV2Fragment
|
||||
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.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.BackgroundViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
|
||||
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.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.ChooseLockSettingsHelper
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -77,6 +82,7 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
|
||||
* children fragments.
|
||||
*/
|
||||
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
private lateinit var fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel
|
||||
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
|
||||
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
||||
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
||||
@@ -84,6 +90,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
private lateinit var foldStateViewModel: FoldStateViewModel
|
||||
private lateinit var orientationStateViewModel: OrientationStateViewModel
|
||||
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||
private lateinit var backgroundViewModel: BackgroundViewModel
|
||||
private val coroutineDispatcher = Dispatchers.Default
|
||||
|
||||
/** Result listener for ChooseLock activity flow. */
|
||||
@@ -101,23 +108,22 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
window.statusBarColor = getBackgroundColor()
|
||||
super.onAttachedToWindow()
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (!isChangingConfigurations) {
|
||||
backgroundViewModel.wentToBackground()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
backgroundViewModel.inForeground()
|
||||
}
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(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?) {
|
||||
val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
|
||||
val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
|
||||
@@ -137,39 +143,28 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
|
||||
val context = applicationContext
|
||||
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 =
|
||||
FingerprintManagerInteractorImpl(
|
||||
context,
|
||||
backgroundDispatcher,
|
||||
fingerprintManager,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context))
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
toReturn == 1
|
||||
}
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context)),
|
||||
PressToAuthProviderImpl(context),
|
||||
enrollType,
|
||||
)
|
||||
|
||||
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
@@ -191,7 +186,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
backgroundDispatcher,
|
||||
interactor,
|
||||
gatekeeperViewModel,
|
||||
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo, /* canSkipConfirm */
|
||||
gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo,
|
||||
enrollType,
|
||||
)
|
||||
)[FingerprintEnrollNavigationViewModel::class.java]
|
||||
|
||||
@@ -207,7 +203,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
this,
|
||||
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
|
||||
interactor,
|
||||
backgroundDispatcher
|
||||
gatekeeperViewModel,
|
||||
navigationViewModel,
|
||||
)
|
||||
)[FingerprintEnrollViewModel::class.java]
|
||||
|
||||
@@ -230,6 +227,16 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
|
||||
OrientationStateViewModel::class.java]
|
||||
|
||||
// Initialize FingerprintEnrollEnrollingViewModel
|
||||
fingerprintEnrollEnrollingViewModel =
|
||||
ViewModelProvider(
|
||||
this,
|
||||
FingerprintEnrollEnrollingViewModel.FingerprintEnrollEnrollingViewModelFactory(
|
||||
fingerprintEnrollViewModel,
|
||||
backgroundViewModel
|
||||
)
|
||||
)[FingerprintEnrollEnrollingViewModel::class.java]
|
||||
|
||||
// Initialize FingerprintEnrollFindSensorViewModel
|
||||
ViewModelProvider(
|
||||
this,
|
||||
@@ -237,48 +244,65 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
navigationViewModel,
|
||||
fingerprintEnrollViewModel,
|
||||
gatekeeperViewModel,
|
||||
backgroundViewModel,
|
||||
accessibilityViewModel,
|
||||
foldStateViewModel,
|
||||
orientationStateViewModel
|
||||
)
|
||||
)[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
|
||||
// Initialize RFPS View Model
|
||||
ViewModelProvider(
|
||||
this,
|
||||
RFPSViewModel.RFPSViewModelFactory(fingerprintEnrollEnrollingViewModel)
|
||||
)[RFPSViewModel::class.java]
|
||||
|
||||
lifecycleScope.launch {
|
||||
navigationViewModel.navigationViewModel.filterNotNull().collect {
|
||||
Log.d(TAG, "navigationStep $it")
|
||||
val isForward = it.forward
|
||||
val currStep = it.currStep
|
||||
val theClass: Class<Fragment>? =
|
||||
when (currStep) {
|
||||
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
|
||||
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
|
||||
Enrollment -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
|
||||
Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (theClass != null) {
|
||||
supportFragmentManager.fragments.onEach { fragment ->
|
||||
supportFragmentManager.beginTransaction().remove(fragment).commit()
|
||||
}
|
||||
supportFragmentManager
|
||||
.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()
|
||||
navigationViewModel.navigationViewModel
|
||||
.filterNotNull()
|
||||
.combine(fingerprintEnrollViewModel.sensorType) { nav, sensorType -> Pair(nav, sensorType) }
|
||||
.collect { (nav, sensorType) ->
|
||||
Log.d(TAG, "navigationStep $nav")
|
||||
fingerprintEnrollViewModel.sensorTypeCached = sensorType
|
||||
val isForward = nav.forward
|
||||
val currStep = nav.currStep
|
||||
val theClass: Class<Fragment>? =
|
||||
when (currStep) {
|
||||
Confirmation -> FingerprintEnrollConfirmationV2Fragment::class.java as Class<Fragment>
|
||||
Education -> FingerprintEnrollFindSensorV2Fragment::class.java as Class<Fragment>
|
||||
is Enrollment -> {
|
||||
when (sensorType) {
|
||||
FingerprintSensorType.REAR -> RFPSEnrollFragment::class.java as Class<Fragment>
|
||||
else -> FingerprintEnrollEnrollingV2Fragment::class.java as Class<Fragment>
|
||||
}
|
||||
}
|
||||
Intro -> FingerprintEnrollIntroV2Fragment::class.java as Class<Fragment>
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (theClass != null) {
|
||||
supportFragmentManager.fragments.onEach { fragment ->
|
||||
supportFragmentManager.beginTransaction().remove(fragment).commit()
|
||||
}
|
||||
|
||||
supportFragmentManager
|
||||
.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 =
|
||||
|
@@ -30,6 +30,7 @@ import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||
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.FingerprintEnrollViewModel
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
@@ -54,23 +55,8 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
|
||||
private var animation: FingerprintFindSensorAnimation? = null
|
||||
|
||||
private var contentLayoutId: Int = -1
|
||||
private lateinit var viewModel: FingerprintEnrollFindSensorViewModel
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
||||
ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
@@ -78,6 +64,18 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): 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 ->
|
||||
val view = it!! as GlifLayout
|
||||
|
||||
@@ -106,7 +104,8 @@ class FingerprintEnrollFindSensorV2Fragment : Fragment() {
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.showRfpsAnimation.collect {
|
||||
animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
|
||||
animation =
|
||||
view.findViewById(R.id.fingerprint_sensor_location_animation)
|
||||
animation!!.startAnimation()
|
||||
}
|
||||
}
|
||||
|
@@ -36,11 +36,11 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.FingerprintEnrollViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Unicorn
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
@@ -120,7 +120,7 @@ class FingerprintEnrollIntroV2Fragment() : Fragment(R.layout.fingerprint_v2_enro
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
combine(
|
||||
navigationViewModel.enrollType,
|
||||
navigationViewModel.fingerprintFlow,
|
||||
fingerprintViewModel.sensorType,
|
||||
) { enrollType, sensorType ->
|
||||
Pair(enrollType, sensorType)
|
||||
|
@@ -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
|
||||
```
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,12 +16,11 @@
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.SetupWizard
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -40,6 +39,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
backgroundViewModel: BackgroundViewModel,
|
||||
accessibilityViewModel: AccessibilityViewModel,
|
||||
foldStateViewModel: FoldStateViewModel,
|
||||
orientationStateViewModel: OrientationStateViewModel
|
||||
@@ -88,6 +88,14 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
/** Represents the stream of showing error dialog. */
|
||||
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 {
|
||||
// Start or end enroll flow
|
||||
viewModelScope.launch {
|
||||
@@ -107,40 +115,58 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
}
|
||||
.collect { token ->
|
||||
if (token != null) {
|
||||
fingerprintEnrollViewModel.startEnroll(token, EnrollReason.FindSensor)
|
||||
canStartEducation()
|
||||
} else {
|
||||
fingerprintEnrollViewModel.stopEnroll()
|
||||
stopEducation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enroll progress flow
|
||||
viewModelScope.launch {
|
||||
combine(
|
||||
navigationViewModel.enrollType,
|
||||
fingerprintEnrollViewModel.enrollFlow.filterNotNull()
|
||||
) { enrollType, enrollFlow ->
|
||||
Pair(enrollType, enrollFlow)
|
||||
}
|
||||
.collect { (enrollType, enrollFlow) ->
|
||||
when (enrollFlow) {
|
||||
// TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding to
|
||||
// Enrolling page. Otherwise Enrolling page will receive the EnrollError.
|
||||
is FingerEnrollStateViewModel.EnrollProgress -> proceedToEnrolling()
|
||||
is FingerEnrollStateViewModel.EnrollError -> {
|
||||
val errMsgId = enrollFlow.errMsgId
|
||||
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
|
||||
proceedToEnrolling()
|
||||
} else {
|
||||
_showErrorDialog.update { Pair(errMsgId, enrollType == SetupWizard) }
|
||||
educationFlowShouldBeRunning.collect {
|
||||
// Only collect the flow when we should be running.
|
||||
if (it) {
|
||||
combine(
|
||||
navigationViewModel.fingerprintFlow,
|
||||
fingerprintEnrollViewModel.educationEnrollFlow.filterNotNull(),
|
||||
) { enrollType, educationFlow ->
|
||||
Pair(enrollType, educationFlow)
|
||||
}
|
||||
.collect { (enrollType, educationFlow) ->
|
||||
when (educationFlow) {
|
||||
// TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding
|
||||
// to
|
||||
// Enrolling page. Otherwise Enrolling page will receive the EnrollError.
|
||||
is FingerEnrollState.EnrollProgress -> proceedToEnrolling()
|
||||
is FingerEnrollState.EnrollError -> {
|
||||
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. */
|
||||
fun proceedToEnrolling() {
|
||||
navigationViewModel.nextStep()
|
||||
@@ -150,6 +176,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
private val backgroundViewModel: BackgroundViewModel,
|
||||
private val accessibilityViewModel: AccessibilityViewModel,
|
||||
private val foldStateViewModel: FoldStateViewModel,
|
||||
private val orientationStateViewModel: OrientationStateViewModel
|
||||
@@ -160,6 +187,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
navigationViewModel,
|
||||
fingerprintEnrollViewModel,
|
||||
gatekeeperViewModel,
|
||||
backgroundViewModel,
|
||||
accessibilityViewModel,
|
||||
foldStateViewModel,
|
||||
orientationStateViewModel
|
||||
|
@@ -17,32 +17,41 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.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 kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
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. */
|
||||
class FingerprintEnrollViewModel(
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
backgroundDispatcher: CoroutineDispatcher,
|
||||
gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
navigationViewModel: FingerprintEnrollNavigationViewModel,
|
||||
) : ViewModel() {
|
||||
|
||||
private var _enrollReason: MutableStateFlow<EnrollReason> =
|
||||
MutableStateFlow(EnrollReason.FindSensor)
|
||||
private var _hardwareAuthToken: MutableStateFlow<ByteArray?> = MutableStateFlow(null)
|
||||
private var _consumerShouldEnroll: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
/**
|
||||
* Cached value of [FingerprintSensorType]
|
||||
*
|
||||
* 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] */
|
||||
val sensorType: Flow<FingerprintSensorType> =
|
||||
@@ -51,47 +60,68 @@ class FingerprintEnrollViewModel(
|
||||
/**
|
||||
* A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
|
||||
* an enrollment process
|
||||
*
|
||||
* This flow should be the only flow which calls enroll().
|
||||
*/
|
||||
val enrollFlow: Flow<FingerEnrollStateViewModel> =
|
||||
combine(_consumerShouldEnroll, _hardwareAuthToken, _enrollReason) {
|
||||
consumerShouldEnroll,
|
||||
hardwareAuthToken,
|
||||
enrollReason ->
|
||||
Triple(consumerShouldEnroll, hardwareAuthToken, enrollReason)
|
||||
val _enrollFlow: Flow<FingerEnrollState> =
|
||||
combine(gatekeeperViewModel.gatekeeperInfo, _enrollReason) { hardwareAuthToken, enrollReason,
|
||||
->
|
||||
Pair(hardwareAuthToken, enrollReason)
|
||||
}
|
||||
.transformLatest {
|
||||
// transformLatest() instead of transform() is used here for cancelling previous enroll()
|
||||
// whenever |consumerShouldEnroll| is changed. Otherwise the latest value will be suspended
|
||||
// since enroll() is an infinite callback flow.
|
||||
(consumerShouldEnroll, hardwareAuthToken, enrollReason) ->
|
||||
if (consumerShouldEnroll && hardwareAuthToken != null) {
|
||||
fingerprintManagerInteractor.enroll(hardwareAuthToken, enrollReason).collect { emit(it) }
|
||||
/** [transformLatest] is used as we want to make sure to cancel previous API call. */
|
||||
(hardwareAuthToken, enrollReason) ->
|
||||
if (hardwareAuthToken is GatekeeperInfo.GatekeeperPasswordInfo && enrollReason != null) {
|
||||
fingerprintManagerInteractor.enroll(hardwareAuthToken.token, 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) {
|
||||
_enrollReason.update { enrollReason }
|
||||
_hardwareAuthToken.update { hardwareAuthToken }
|
||||
// Update _consumerShouldEnroll after updating the other values.
|
||||
_consumerShouldEnroll.update { true }
|
||||
}
|
||||
/**
|
||||
* This flow will kick off education when
|
||||
* 1) There is an active subscriber to this flow
|
||||
* 2) shouldEnroll is true and we are on the FindSensor step
|
||||
*/
|
||||
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() {
|
||||
_consumerShouldEnroll.update { false }
|
||||
}
|
||||
/**
|
||||
* This flow will kick off enrollment when
|
||||
* 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(
|
||||
val interactor: FingerprintManagerInteractor,
|
||||
val backgroundDispatcher: CoroutineDispatcher
|
||||
val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
val navigationViewModel: FingerprintEnrollNavigationViewModel,
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: Class<T>,
|
||||
): T {
|
||||
return FingerprintEnrollViewModel(interactor, backgroundDispatcher) as T
|
||||
return FingerprintEnrollViewModel(
|
||||
interactor,
|
||||
gatekeeperViewModel,
|
||||
navigationViewModel,
|
||||
)
|
||||
as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,30 +21,19 @@ import androidx.lifecycle.ViewModel
|
||||
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.model.FingerprintFlow
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
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
|
||||
* Fingerprint Enrollment flow
|
||||
@@ -53,31 +42,26 @@ class FingerprintEnrollNavigationViewModel(
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
private val canSkipConfirm: Boolean
|
||||
private val firstStep: NextStepViewModel,
|
||||
private val navState: NavState,
|
||||
private val theFingerprintFlow: FingerprintFlow,
|
||||
) : ViewModel() {
|
||||
|
||||
private class InternalNavigationStep(
|
||||
lastStep: NextStepViewModel,
|
||||
nextStep: NextStepViewModel,
|
||||
forward: Boolean,
|
||||
var canNavigate: Boolean
|
||||
var canNavigate: Boolean,
|
||||
) : NavigationStep(lastStep, nextStep, forward)
|
||||
|
||||
private var _enrollType = MutableStateFlow<EnrollType?>(Default)
|
||||
private var _fingerprintFlow = MutableStateFlow<FingerprintFlow?>(theFingerprintFlow)
|
||||
|
||||
/** A flow that indicates the [EnrollType] */
|
||||
val enrollType: Flow<EnrollType?> = _enrollType.asStateFlow()
|
||||
|
||||
private var navState = NavState(canSkipConfirm)
|
||||
/** A flow that indicates the [FingerprintFlow] */
|
||||
val fingerprintFlow: Flow<FingerprintFlow?> = _fingerprintFlow.asStateFlow()
|
||||
|
||||
private val _navigationStep =
|
||||
MutableStateFlow(
|
||||
InternalNavigationStep(
|
||||
PlaceHolderState,
|
||||
Start.next(navState),
|
||||
forward = false,
|
||||
canNavigate = true
|
||||
)
|
||||
InternalNavigationStep(PlaceHolderState, firstStep, forward = false, canNavigate = true)
|
||||
)
|
||||
|
||||
init {
|
||||
@@ -96,6 +80,10 @@ class FingerprintEnrollNavigationViewModel(
|
||||
*/
|
||||
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. */
|
||||
fun nextStep() {
|
||||
viewModelScope.launch {
|
||||
@@ -130,6 +118,7 @@ class FingerprintEnrollNavigationViewModel(
|
||||
private val fingerprintManagerInteractor: FingerprintManagerInteractor,
|
||||
private val fingerprintGatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||
private val canSkipConfirm: Boolean,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -137,11 +126,14 @@ class FingerprintEnrollNavigationViewModel(
|
||||
modelClass: Class<T>,
|
||||
): T {
|
||||
|
||||
val navState = NavState(canSkipConfirm)
|
||||
return FingerprintEnrollNavigationViewModel(
|
||||
backgroundDispatcher,
|
||||
fingerprintManagerInteractor,
|
||||
fingerprintGatekeeperViewModel,
|
||||
canSkipConfirm,
|
||||
Start.next(navState),
|
||||
navState,
|
||||
fingerprintFlow,
|
||||
)
|
||||
as T
|
||||
}
|
||||
|
@@ -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
|
||||
* needs to [LaunchConfirmDeviceCredential] if not, it will go to [Intro]
|
||||
*/
|
||||
object Start : NextStepViewModel() {
|
||||
data object Start : NextStepViewModel() {
|
||||
override fun next(state: NavState): NextStepViewModel =
|
||||
if (state.confirmedDevice) Intro else LaunchConfirmDeviceCredential
|
||||
|
||||
@@ -71,19 +71,19 @@ class Finish(val resultCode: Int?) : NextStepViewModel() {
|
||||
}
|
||||
|
||||
/** State for the FingerprintEnrollment introduction */
|
||||
object Intro : NextStepViewModel() {
|
||||
data object Intro : NextStepViewModel() {
|
||||
override fun next(state: NavState): NextStepViewModel = Education
|
||||
override fun prev(state: NavState): NextStepViewModel = Finish(null)
|
||||
}
|
||||
|
||||
/** State for the FingerprintEnrollment education */
|
||||
object Education : NextStepViewModel() {
|
||||
data object Education : NextStepViewModel() {
|
||||
override fun next(state: NavState): NextStepViewModel = Enrollment
|
||||
override fun prev(state: NavState): NextStepViewModel = Intro
|
||||
}
|
||||
|
||||
/** State for the FingerprintEnrollment enrollment */
|
||||
object Enrollment : NextStepViewModel() {
|
||||
data object Enrollment : NextStepViewModel() {
|
||||
override fun next(state: NavState): NextStepViewModel = Confirmation
|
||||
override fun prev(state: NavState): NextStepViewModel = Education
|
||||
}
|
||||
|
@@ -19,8 +19,8 @@ package com.android.settings.biometrics.fingerprint2.ui.settings.binder
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
|
||||
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.viewmodel.EnrollAdditionalFingerprint
|
||||
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 */
|
||||
fun setResultExternal(resultCode: Int)
|
||||
/** Indicates the settings UI should be shown */
|
||||
fun showSettings(enrolledFingerprints: List<FingerprintViewModel>)
|
||||
fun showSettings(enrolledFingerprints: List<FingerprintData>)
|
||||
/** Updates the add fingerprints preference */
|
||||
fun updateAddFingerprintsPreference(canEnroll: Boolean, maxFingerprints: Int)
|
||||
/** Updates the sfps fingerprints preference */
|
||||
fun updateSfpsPreference(isSfpsPrefVisible: Boolean)
|
||||
/** 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 */
|
||||
suspend fun highlightPref(fingerId: Int)
|
||||
/** 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 */
|
||||
suspend fun askUserToRenameDialog(
|
||||
fingerprintViewModel: FingerprintViewModel
|
||||
): Pair<FingerprintViewModel, String>?
|
||||
fingerprintViewModel: FingerprintData
|
||||
): Pair<FingerprintData, String>?
|
||||
}
|
||||
|
||||
fun bind(
|
||||
@@ -131,10 +131,10 @@ object FingerprintSettingsViewBinder {
|
||||
lifecycleScope.launch {
|
||||
viewModel.authFlow.filterNotNull().collect {
|
||||
when (it) {
|
||||
is FingerprintAuthAttemptViewModel.Success -> {
|
||||
is FingerprintAuthAttemptModel.Success -> {
|
||||
view.highlightPref(it.fingerId)
|
||||
}
|
||||
is FingerprintAuthAttemptViewModel.Error -> {
|
||||
is FingerprintAuthAttemptModel.Error -> {
|
||||
if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
|
||||
view.userLockout(it)
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ import android.os.Bundle
|
||||
import android.os.UserManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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 kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
@@ -34,7 +34,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
private const val KEY_IS_LAST_FINGERPRINT = "IS_LAST_FINGERPRINT"
|
||||
|
||||
class FingerprintDeletionDialog : InstrumentedDialogFragment() {
|
||||
private lateinit var fingerprintViewModel: FingerprintViewModel
|
||||
private lateinit var fingerprintViewModel: FingerprintData
|
||||
private var isLastFingerprint: Boolean = false
|
||||
private lateinit var alertDialog: AlertDialog
|
||||
lateinit var onClickListener: DialogInterface.OnClickListener
|
||||
@@ -51,7 +51,7 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
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)
|
||||
val title = getString(R.string.fingerprint_delete_title, fingerprintViewModel.name)
|
||||
var message = getString(R.string.fingerprint_v2_delete_message, fingerprintViewModel.name)
|
||||
@@ -95,9 +95,9 @@ class FingerprintDeletionDialog : InstrumentedDialogFragment() {
|
||||
companion object {
|
||||
private const val KEY_FINGERPRINT = "fingerprint"
|
||||
suspend fun showInstance(
|
||||
fp: FingerprintViewModel,
|
||||
lastFingerprint: Boolean,
|
||||
target: FingerprintSettingsV2Fragment,
|
||||
fp: FingerprintData,
|
||||
lastFingerprint: Boolean,
|
||||
target: FingerprintSettingsV2Fragment,
|
||||
) = suspendCancellableCoroutine { continuation ->
|
||||
val dialog = FingerprintDeletionDialog()
|
||||
dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
|
||||
|
@@ -22,7 +22,7 @@ import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
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 kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -30,10 +30,10 @@ import kotlinx.coroutines.launch
|
||||
private const val TAG = "FingerprintSettingsPreference"
|
||||
|
||||
class FingerprintSettingsPreference(
|
||||
context: Context,
|
||||
val fingerprintViewModel: FingerprintViewModel,
|
||||
val fragment: FingerprintSettingsV2Fragment,
|
||||
val isLastFingerprint: Boolean
|
||||
context: Context,
|
||||
val fingerprintViewModel: FingerprintData,
|
||||
val fragment: FingerprintSettingsV2Fragment,
|
||||
val isLastFingerprint: Boolean
|
||||
) : TwoTargetPreference(context) {
|
||||
private lateinit var myView: View
|
||||
|
||||
@@ -79,7 +79,7 @@ class FingerprintSettingsPreference(
|
||||
return FingerprintDeletionDialog.showInstance(fingerprintViewModel, isLastFingerprint, fragment)
|
||||
}
|
||||
|
||||
suspend fun askUserToRenameDialog(): Pair<FingerprintViewModel, String>? {
|
||||
suspend fun askUserToRenameDialog(): Pair<FingerprintData, String>? {
|
||||
return FingerprintSettingsRenameDialog.showInstance(fingerprintViewModel, fragment)
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ import android.util.Log
|
||||
import android.widget.ImeAwareEditText
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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 kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
@@ -46,7 +46,7 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
Log.d(TAG, "onCreateDialog $this")
|
||||
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 alertDialog =
|
||||
@@ -101,7 +101,7 @@ class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
|
||||
companion object {
|
||||
private const val KEY_FINGERPRINT = "fingerprint"
|
||||
|
||||
suspend fun showInstance(fp: FingerprintViewModel, target: FingerprintSettingsV2Fragment) =
|
||||
suspend fun showInstance(fp: FingerprintData, target: FingerprintSettingsV2Fragment) =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val dialog = FingerprintSettingsRenameDialog()
|
||||
val onClick =
|
||||
|
@@ -46,8 +46,10 @@ import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.repository.PressToAuthProviderImpl
|
||||
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.viewmodel.FingerprintSettingsNavigationViewModel
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -186,40 +188,46 @@ class FingerprintSettingsV2Fragment :
|
||||
val backgroundDispatcher = Dispatchers.IO
|
||||
val activity = requireActivity()
|
||||
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 =
|
||||
FingerprintManagerInteractorImpl(
|
||||
context.applicationContext,
|
||||
backgroundDispatcher,
|
||||
fingerprintManager,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext))
|
||||
) {
|
||||
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
|
||||
}
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
|
||||
PressToAuthProviderImpl(context),
|
||||
isAnySuw
|
||||
)
|
||||
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
|
||||
@@ -292,18 +300,18 @@ class FingerprintSettingsV2Fragment :
|
||||
}
|
||||
|
||||
/** Used to indicate that preference has been clicked */
|
||||
fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
|
||||
fun onPrefClicked(fingerprintViewModel: FingerprintData) {
|
||||
Log.d(TAG, "onPrefClicked(${fingerprintViewModel})")
|
||||
settingsViewModel.onPrefClicked(fingerprintViewModel)
|
||||
}
|
||||
|
||||
/** Used to indicate that a delete pref has been clicked */
|
||||
fun onDeletePrefClicked(fingerprintViewModel: FingerprintViewModel) {
|
||||
fun onDeletePrefClicked(fingerprintViewModel: FingerprintData) {
|
||||
Log.d(TAG, "onDeletePrefClicked(${fingerprintViewModel})")
|
||||
settingsViewModel.onDeleteClicked(fingerprintViewModel)
|
||||
}
|
||||
|
||||
override fun showSettings(enrolledFingerprints: List<FingerprintViewModel>) {
|
||||
override fun showSettings(enrolledFingerprints: List<FingerprintData>) {
|
||||
val category =
|
||||
this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
|
||||
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})")
|
||||
|
||||
try {
|
||||
@@ -446,8 +454,8 @@ class FingerprintSettingsV2Fragment :
|
||||
}
|
||||
|
||||
override suspend fun askUserToRenameDialog(
|
||||
fingerprintViewModel: FingerprintViewModel
|
||||
): Pair<FingerprintViewModel, String>? {
|
||||
fingerprintViewModel: FingerprintData
|
||||
): Pair<FingerprintData, String>? {
|
||||
Log.d(TAG, "showing rename dialog for (${fingerprintViewModel})")
|
||||
try {
|
||||
val toReturn =
|
||||
|
@@ -22,8 +22,8 @@ import androidx.lifecycle.ViewModel
|
||||
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.model.FingerprintAuthAttemptViewModel
|
||||
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.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -53,11 +53,11 @@ class FingerprintSettingsViewModel(
|
||||
private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val navigationViewModel: FingerprintSettingsNavigationViewModel,
|
||||
) : ViewModel() {
|
||||
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintViewModel>?> =
|
||||
private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
|
||||
MutableStateFlow(null)
|
||||
|
||||
/** Represents the stream of enrolled fingerprints. */
|
||||
val enrolledFingerprints: Flow<List<FingerprintViewModel>> =
|
||||
val enrolledFingerprints: Flow<List<FingerprintData>> =
|
||||
_enrolledFingerprints.asStateFlow().filterNotNull().filterOnlyWhenSettingsIsShown()
|
||||
|
||||
/** Represents the stream of the information of "Add Fingerprint" preference. */
|
||||
@@ -95,10 +95,10 @@ class FingerprintSettingsViewModel(
|
||||
private val _sensorNullOrEmpty: Flow<Boolean> =
|
||||
fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
|
||||
|
||||
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
|
||||
private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
|
||||
MutableStateFlow(null)
|
||||
|
||||
private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
|
||||
private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptModel.Success?> =
|
||||
MutableSharedFlow()
|
||||
|
||||
private val _attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||
@@ -164,7 +164,7 @@ class FingerprintSettingsViewModel(
|
||||
.distinctUntilChanged()
|
||||
|
||||
/** Represents a consistent stream of authentication attempts. */
|
||||
val authFlow: Flow<FingerprintAuthAttemptViewModel> =
|
||||
val authFlow: Flow<FingerprintAuthAttemptModel> =
|
||||
canAuthenticate
|
||||
.transformLatest {
|
||||
try {
|
||||
@@ -173,11 +173,11 @@ class FingerprintSettingsViewModel(
|
||||
Log.d(TAG, "canAuthenticate authing")
|
||||
attemptingAuth()
|
||||
when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
|
||||
is FingerprintAuthAttemptViewModel.Success -> {
|
||||
is FingerprintAuthAttemptModel.Success -> {
|
||||
onAuthSuccess(authAttempt)
|
||||
emit(authAttempt)
|
||||
}
|
||||
is FingerprintAuthAttemptViewModel.Error -> {
|
||||
is FingerprintAuthAttemptModel.Error -> {
|
||||
if (authAttempt.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
|
||||
lockout(authAttempt)
|
||||
emit(authAttempt)
|
||||
@@ -219,7 +219,7 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
|
||||
/** The fingerprint delete button has been clicked. */
|
||||
fun onDeleteClicked(fingerprintViewModel: FingerprintViewModel) {
|
||||
fun onDeleteClicked(fingerprintViewModel: FingerprintData) {
|
||||
viewModelScope.launch {
|
||||
if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
|
||||
_isShowingDialog.tryEmit(PreferenceViewModel.DeleteDialog(fingerprintViewModel))
|
||||
@@ -230,7 +230,7 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
|
||||
/** The rename fingerprint dialog has been clicked. */
|
||||
fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
|
||||
fun onPrefClicked(fingerprintViewModel: FingerprintData) {
|
||||
viewModelScope.launch {
|
||||
if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
|
||||
_isShowingDialog.tryEmit(PreferenceViewModel.RenameDialog(fingerprintViewModel))
|
||||
@@ -241,7 +241,7 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
|
||||
/** A request to delete a fingerprint */
|
||||
fun deleteFingerprint(fp: FingerprintViewModel) {
|
||||
fun deleteFingerprint(fp: FingerprintData) {
|
||||
viewModelScope.launch(backgroundDispatcher) {
|
||||
if (fingerprintManagerInteractor.removeFingerprint(fp)) {
|
||||
updateEnrolledFingerprints()
|
||||
@@ -250,7 +250,7 @@ class FingerprintSettingsViewModel(
|
||||
}
|
||||
|
||||
/** A request to rename a fingerprint */
|
||||
fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
|
||||
fun renameFingerprint(fp: FingerprintData, newName: String) {
|
||||
viewModelScope.launch {
|
||||
fingerprintManagerInteractor.renameFingerprint(fp, newName)
|
||||
updateEnrolledFingerprints()
|
||||
@@ -261,12 +261,12 @@ class FingerprintSettingsViewModel(
|
||||
_attemptsSoFar.update { it + 1 }
|
||||
}
|
||||
|
||||
private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
|
||||
private suspend fun onAuthSuccess(success: FingerprintAuthAttemptModel.Success) {
|
||||
_authSucceeded.emit(success)
|
||||
_attemptsSoFar.update { 0 }
|
||||
}
|
||||
|
||||
private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
|
||||
private fun lockout(attemptViewModel: FingerprintAuthAttemptModel.Error) {
|
||||
_isLockedOut.update { attemptViewModel }
|
||||
}
|
||||
|
||||
|
@@ -16,15 +16,15 @@
|
||||
|
||||
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. */
|
||||
sealed class PreferenceViewModel {
|
||||
data class RenameDialog(
|
||||
val fingerprintViewModel: FingerprintViewModel,
|
||||
val fingerprintViewModel: FingerprintData,
|
||||
) : PreferenceViewModel()
|
||||
|
||||
data class DeleteDialog(
|
||||
val fingerprintViewModel: FingerprintViewModel,
|
||||
val fingerprintViewModel: FingerprintData,
|
||||
) : PreferenceViewModel()
|
||||
}
|
||||
|
@@ -33,12 +33,15 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
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.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.FingerprintScrollViewModel
|
||||
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.google.android.setupdesign.GlifLayout
|
||||
import com.google.android.setupdesign.template.RequireScrollMixin
|
||||
@@ -65,9 +68,12 @@ class FingerprintEnrollIntroFragmentTest {
|
||||
backgroundDispatcher,
|
||||
interactor,
|
||||
gatekeeperViewModel,
|
||||
canSkipConfirm = true,
|
||||
Intro,
|
||||
NavState(true),
|
||||
Default,
|
||||
)
|
||||
private var fingerprintViewModel = FingerprintEnrollViewModel(interactor, backgroundDispatcher)
|
||||
private var fingerprintViewModel =
|
||||
FingerprintEnrollViewModel(interactor, gatekeeperViewModel, navigationViewModel)
|
||||
private var fingerprintScrollViewModel = FingerprintScrollViewModel()
|
||||
|
||||
@Before
|
||||
|
@@ -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.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.android.systemui.biometrics.shared.model.SensorStrength
|
||||
@@ -32,10 +32,11 @@ import kotlinx.coroutines.flow.flowOf
|
||||
class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
|
||||
var enrollableFingerprints: Int = 5
|
||||
var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
|
||||
var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
|
||||
var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
|
||||
var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
|
||||
val enrollStateViewModel = FingerEnrollStateViewModel.EnrollProgress(1)
|
||||
var authenticateAttempt = FingerprintAuthAttemptModel.Success(1)
|
||||
var enrollStateViewModel: List<FingerEnrollState> =
|
||||
listOf(FingerEnrollState.EnrollProgress(5, 5))
|
||||
var pressToAuthEnabled = true
|
||||
|
||||
var sensorProp =
|
||||
@@ -46,7 +47,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
FingerprintSensorType.POWER_BUTTON
|
||||
)
|
||||
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptViewModel {
|
||||
override suspend fun authenticate(): FingerprintAuthAttemptModel {
|
||||
return authenticateAttempt
|
||||
}
|
||||
|
||||
@@ -54,7 +55,7 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
return challengeToGenerate
|
||||
}
|
||||
|
||||
override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
|
||||
override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
|
||||
emit(enrolledFingerprintsInternal)
|
||||
}
|
||||
|
||||
@@ -62,24 +63,22 @@ class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
|
||||
emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
|
||||
}
|
||||
|
||||
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow {
|
||||
emit(sensorProp)
|
||||
}
|
||||
override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
|
||||
|
||||
override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
|
||||
|
||||
override suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
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)
|
||||
}
|
||||
|
||||
override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
|
||||
override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
|
||||
if (enrolledFingerprintsInternal.remove(fp)) {
|
||||
enrolledFingerprintsInternal.add(FingerprintViewModel(newName, fp.fingerId, fp.deviceId))
|
||||
enrolledFingerprintsInternal.add(FingerprintData(newName, fp.fingerId, fp.deviceId))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,12 +26,14 @@ import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
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.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.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
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.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
@@ -69,7 +71,11 @@ class FingerprintManagerInteractorTest {
|
||||
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
|
||||
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private var pressToAuthProvider = { true }
|
||||
private var pressToAuthProvider =
|
||||
object : PressToAuthProvider {
|
||||
override val isEnabled: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -80,6 +86,7 @@ class FingerprintManagerInteractorTest {
|
||||
fingerprintManager,
|
||||
gateKeeperPasswordProvider,
|
||||
pressToAuthProvider,
|
||||
Default,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -164,7 +171,7 @@ class FingerprintManagerInteractorTest {
|
||||
@Test
|
||||
fun testRemoveFingerprint_succeeds() =
|
||||
testScope.runTest {
|
||||
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
||||
val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
|
||||
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
||||
|
||||
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
|
||||
@@ -187,7 +194,7 @@ class FingerprintManagerInteractorTest {
|
||||
@Test
|
||||
fun testRemoveFingerprint_fails() =
|
||||
testScope.runTest {
|
||||
val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
|
||||
val fingerprintViewModelToRemove = FingerprintData("Finger 2", 1, 2L)
|
||||
val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
|
||||
|
||||
val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> = argumentCaptor()
|
||||
@@ -214,7 +221,7 @@ class FingerprintManagerInteractorTest {
|
||||
@Test
|
||||
fun testRenameFingerprint_succeeds() =
|
||||
testScope.runTest {
|
||||
val fingerprintToRename = FingerprintViewModel("Finger 2", 1, 2L)
|
||||
val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
|
||||
|
||||
underTest.renameFingerprint(fingerprintToRename, "Woo")
|
||||
|
||||
@@ -226,7 +233,7 @@ class FingerprintManagerInteractorTest {
|
||||
testScope.runTest {
|
||||
val fingerprint = Fingerprint("Woooo", 100, 101L)
|
||||
|
||||
var result: FingerprintAuthAttemptViewModel? = null
|
||||
var result: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { result = underTest.authenticate() }
|
||||
|
||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
|
||||
@@ -247,13 +254,13 @@ class FingerprintManagerInteractorTest {
|
||||
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
assertThat(result).isEqualTo(FingerprintAuthAttemptViewModel.Success(fingerprint.biometricId))
|
||||
assertThat(result).isEqualTo(FingerprintAuthAttemptModel.Success(fingerprint.biometricId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAuth_lockout() =
|
||||
testScope.runTest {
|
||||
var result: FingerprintAuthAttemptViewModel? = null
|
||||
var result: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { result = underTest.authenticate() }
|
||||
|
||||
val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
|
||||
@@ -274,7 +281,7 @@ class FingerprintManagerInteractorTest {
|
||||
job.cancelAndJoin()
|
||||
assertThat(result)
|
||||
.isEqualTo(
|
||||
FingerprintAuthAttemptViewModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
|
||||
FingerprintAuthAttemptModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -282,7 +289,7 @@ class FingerprintManagerInteractorTest {
|
||||
fun testEnroll_progress() =
|
||||
testScope.runTest {
|
||||
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 enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
|
||||
runCurrent()
|
||||
@@ -299,14 +306,14 @@ class FingerprintManagerInteractorTest {
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollProgress(1))
|
||||
assertThat(result).isEqualTo(FingerEnrollState.EnrollProgress(1, 2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnroll_help() =
|
||||
testScope.runTest {
|
||||
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 enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
|
||||
runCurrent()
|
||||
@@ -323,14 +330,14 @@ class FingerprintManagerInteractorTest {
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollHelp(-1, "help"))
|
||||
assertThat(result).isEqualTo(FingerEnrollState.EnrollHelp(-1, "help"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnroll_error() =
|
||||
testScope.runTest {
|
||||
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 enrollCallback: ArgumentCaptor<FingerprintManager.EnrollmentCallback> = argumentCaptor()
|
||||
runCurrent()
|
||||
@@ -343,17 +350,20 @@ class FingerprintManagerInteractorTest {
|
||||
capture(enrollCallback),
|
||||
eq(FingerprintManager.ENROLL_FIND_SENSOR)
|
||||
)
|
||||
enrollCallback.value.onEnrollmentError(-2, "error")
|
||||
enrollCallback.value.onEnrollmentError(-1, "error")
|
||||
runCurrent()
|
||||
job.cancelAndJoin()
|
||||
|
||||
assertThat(result).isEqualTo(FingerEnrollStateViewModel.EnrollError(-2, "error"))
|
||||
assertThat(result).isInstanceOf(FingerEnrollState.EnrollError::class.java)
|
||||
}
|
||||
|
||||
private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
|
||||
|
||||
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||
|
||||
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
|
||||
|
||||
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
|
||||
|
||||
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
|
||||
ArgumentCaptor.forClass(T::class.java)
|
||||
}
|
||||
|
@@ -21,7 +21,9 @@ import android.content.res.Configuration
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
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.BackgroundViewModel
|
||||
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.FingerprintEnrollNavigationViewModel
|
||||
@@ -70,6 +72,7 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
private lateinit var foldStateViewModel: FoldStateViewModel
|
||||
private lateinit var orientationStateViewModel: OrientationStateViewModel
|
||||
private lateinit var underTest: FingerprintEnrollFindSensorViewModel
|
||||
private lateinit var backgroundViewModel: BackgroundViewModel
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
private val accessibilityManager: AccessibilityManager =
|
||||
context.getSystemService(AccessibilityManager::class.java)!!
|
||||
@@ -93,12 +96,18 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
fakeFingerprintManagerInteractor,
|
||||
gatekeeperViewModel,
|
||||
canSkipConfirm = true,
|
||||
Default,
|
||||
)
|
||||
.create(FingerprintEnrollNavigationViewModel::class.java)
|
||||
|
||||
backgroundViewModel =
|
||||
BackgroundViewModel.BackgroundViewModelFactory().create(BackgroundViewModel::class.java)
|
||||
backgroundViewModel.inForeground()
|
||||
enrollViewModel =
|
||||
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
|
||||
fakeFingerprintManagerInteractor,
|
||||
backgroundDispatcher
|
||||
gatekeeperViewModel,
|
||||
navigationViewModel,
|
||||
)
|
||||
.create(FingerprintEnrollViewModel::class.java)
|
||||
accessibilityViewModel =
|
||||
@@ -114,6 +123,7 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
navigationViewModel,
|
||||
enrollViewModel,
|
||||
gatekeeperViewModel,
|
||||
backgroundViewModel,
|
||||
accessibilityViewModel,
|
||||
foldStateViewModel,
|
||||
orientationStateViewModel
|
||||
@@ -123,6 +133,7 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
// Navigate to Education page
|
||||
navigationViewModel.nextStep()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ package com.android.settings.fingerprint2.ui.settings
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
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.FingerprintSettingsNavigationViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FinishSettings
|
||||
@@ -208,7 +208,7 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
fun enrollAdditionalFingerprints_fails() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
@@ -227,7 +227,7 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
fun enrollAdditional_success() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
@@ -245,7 +245,7 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
@@ -320,7 +320,7 @@ class FingerprintSettingsNavigationViewModelTest {
|
||||
fun showSettings_shouldFinish() =
|
||||
testScope.runTest {
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
|
||||
var nextStep: NextStepViewModel? = null
|
||||
val job = launch { underTest.nextStep.collect { nextStep = it } }
|
||||
|
@@ -17,8 +17,8 @@
|
||||
package com.android.settings.fingerprint2.ui.settings
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
|
||||
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.FingerprintSettingsViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.PreferenceViewModel
|
||||
@@ -103,7 +103,7 @@ class FingerprintSettingsViewModelTest {
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
@@ -114,7 +114,7 @@ class FingerprintSettingsViewModelTest {
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
|
||||
underTest.shouldAuthenticate(true)
|
||||
@@ -139,7 +139,7 @@ class FingerprintSettingsViewModelTest {
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC,
|
||||
)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
|
||||
underTest =
|
||||
FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
|
||||
@@ -150,7 +150,7 @@ class FingerprintSettingsViewModelTest {
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
|
||||
underTest.shouldAuthenticate(true)
|
||||
@@ -173,8 +173,8 @@ class FingerprintSettingsViewModelTest {
|
||||
FingerprintSensorType.POWER_BUTTON
|
||||
)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L))
|
||||
val success = FingerprintAuthAttemptViewModel.Success(1)
|
||||
mutableListOf(FingerprintData("a", 1, 3L))
|
||||
val success = FingerprintAuthAttemptModel.Success(1)
|
||||
fakeFingerprintManagerInteractor.authenticateAttempt = success
|
||||
|
||||
underTest =
|
||||
@@ -186,7 +186,7 @@ class FingerprintSettingsViewModelTest {
|
||||
)
|
||||
.create(FingerprintSettingsViewModel::class.java)
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
|
||||
val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
|
||||
underTest.shouldAuthenticate(true)
|
||||
@@ -200,7 +200,7 @@ class FingerprintSettingsViewModelTest {
|
||||
|
||||
@Test
|
||||
fun deleteDialog_showAndDismiss() = runTest {
|
||||
val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
|
||||
val fingerprintToDelete = FingerprintData("A", 1, 10L)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(fingerprintToDelete)
|
||||
|
||||
@@ -236,7 +236,7 @@ class FingerprintSettingsViewModelTest {
|
||||
|
||||
@Test
|
||||
fun renameDialog_showAndDismiss() = runTest {
|
||||
val fingerprintToRename = FingerprintViewModel("World", 1, 10L)
|
||||
val fingerprintToRename = FingerprintData("World", 1, 10L)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(fingerprintToRename)
|
||||
|
||||
@@ -274,7 +274,7 @@ class FingerprintSettingsViewModelTest {
|
||||
|
||||
@Test
|
||||
fun testTwoDialogsCannotShow_atSameTime() = runTest {
|
||||
val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
|
||||
val fingerprintToDelete = FingerprintData("A", 1, 10L)
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
|
||||
mutableListOf(fingerprintToDelete)
|
||||
|
||||
@@ -311,9 +311,9 @@ class FingerprintSettingsViewModelTest {
|
||||
fun authenticatePauses_whenPaused() =
|
||||
testScope.runTest {
|
||||
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 } }
|
||||
|
||||
@@ -325,7 +325,7 @@ class FingerprintSettingsViewModelTest {
|
||||
assertThat(authAttempt).isEqualTo(success)
|
||||
|
||||
fakeFingerprintManagerInteractor.authenticateAttempt =
|
||||
FingerprintAuthAttemptViewModel.Success(10)
|
||||
FingerprintAuthAttemptModel.Success(10)
|
||||
underTest.shouldAuthenticate(false)
|
||||
advanceTimeBy(400)
|
||||
runCurrent()
|
||||
@@ -340,7 +340,7 @@ class FingerprintSettingsViewModelTest {
|
||||
testScope.runTest {
|
||||
val fingerprints = setupAuth()
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
|
||||
underTest.shouldAuthenticate(true)
|
||||
navigationViewModel.onConfirmDevice(true, 10L)
|
||||
@@ -357,7 +357,7 @@ class FingerprintSettingsViewModelTest {
|
||||
testScope.runTest {
|
||||
val fingerprints = setupAuth()
|
||||
|
||||
var authAttempt: FingerprintAuthAttemptViewModel? = null
|
||||
var authAttempt: FingerprintAuthAttemptModel? = null
|
||||
val job = launch { underTest.authFlow.take(1).collectLatest { authAttempt = it } }
|
||||
underTest.shouldAuthenticate(true)
|
||||
navigationViewModel.onConfirmDevice(true, 10L)
|
||||
@@ -370,7 +370,7 @@ class FingerprintSettingsViewModelTest {
|
||||
assertThat(authAttempt).isEqualTo(null)
|
||||
}
|
||||
|
||||
private fun setupAuth(): MutableList<FingerprintViewModel> {
|
||||
private fun setupAuth(): MutableList<FingerprintData> {
|
||||
fakeFingerprintManagerInteractor.sensorProp =
|
||||
FingerprintSensor(
|
||||
0 /* sensorId */,
|
||||
@@ -379,9 +379,9 @@ class FingerprintSettingsViewModelTest {
|
||||
FingerprintSensorType.POWER_BUTTON
|
||||
)
|
||||
val fingerprints =
|
||||
mutableListOf(FingerprintViewModel("a", 1, 3L), FingerprintViewModel("b", 2, 5L))
|
||||
mutableListOf(FingerprintData("a", 1, 3L), FingerprintData("b", 2, 5L))
|
||||
fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = fingerprints
|
||||
val success = FingerprintAuthAttemptViewModel.Success(1)
|
||||
val success = FingerprintAuthAttemptModel.Success(1)
|
||||
fakeFingerprintManagerInteractor.authenticateAttempt = success
|
||||
|
||||
underTest =
|
||||
|
Reference in New Issue
Block a user