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