diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f629a365099..a438fdd1f76 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2788,6 +2788,7 @@ diff --git a/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml new file mode 100644 index 00000000000..86768d6b43f --- /dev/null +++ b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml index ddd2c30d807..ab8fb2c3ae5 100644 --- a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml +++ b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml @@ -18,7 +18,7 @@ +} diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt new file mode 100644 index 00000000000..9b74813e33f --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/UdfpsEnrollDebugRepository.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 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.data.repository + +import android.graphics.Point +import android.graphics.Rect +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.systemui.biometrics.shared.model.FingerprintSensor +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf + +/** + * This class is used to simulate enroll data. This has two major use cases. 1). Ease of Development + * 2). Bug Fixes + */ +class UdfpsEnrollDebugRepositoryImpl : + FingerprintEnrollInteractor, FingerprintSensorRepository, SimulatedTouchEventsRepository { + + override suspend fun enroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason) = flow { + emit(FingerEnrollState.OverlayShown) + delay(200) + emit(FingerEnrollState.EnrollHelp(helpMsgId, "Hello world")) + delay(200) + emit(FingerEnrollState.EnrollProgress(15, 16)) + delay(300) + emit(FingerEnrollState.EnrollHelp(helpMsgId, "Hello world")) + delay(1000) + emit(FingerEnrollState.EnrollProgress(14, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(13, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(12, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(11, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(10, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(9, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(8, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(7, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(6, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(5, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(4, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(3, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(2, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(1, 16)) + delay(500) + emit(FingerEnrollState.EnrollProgress(0, 16)) + } + + /** Provides touch events to the UdfpsEnrollFragment */ + override val touchExplorationDebug: Flow = flow { + delay(2000) + emit(pointToLeftOfSensor(sensorRect)) + delay(2000) + emit(pointBelowSensor(sensorRect)) + delay(2000) + emit(pointToRightOfSensor(sensorRect)) + delay(2000) + emit(pointAboveSensor(sensorRect)) + } + + override val fingerprintSensor: Flow = flowOf(sensorProps) + + private fun pointToLeftOfSensor(sensorLocation: Rect) = + Point(sensorLocation.right + 5, sensorLocation.centerY()) + + private fun pointToRightOfSensor(sensorLocation: Rect) = + Point(sensorLocation.left - 5, sensorLocation.centerY()) + + private fun pointBelowSensor(sensorLocation: Rect) = + Point(sensorLocation.centerX(), sensorLocation.bottom + 5) + + private fun pointAboveSensor(sensorLocation: Rect) = + Point(sensorLocation.centerX(), sensorLocation.top - 5) + + companion object { + + private val helpMsgId: Int = 1 + private val sensorLocationInternal = Pair(540, 1713) + private val sensorRadius = 100 + private val sensorRect = + Rect( + this.sensorLocationInternal.first - sensorRadius, + this.sensorLocationInternal.second - sensorRadius, + this.sensorLocationInternal.first + sensorRadius, + this.sensorLocationInternal.second + sensorRadius, + ) + val sensorProps = + FingerprintSensor( + 1, + SensorStrength.STRONG, + 5, + FingerprintSensorType.UDFPS_OPTICAL, + sensorRect, + sensorRadius, + ) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt new file mode 100644 index 00000000000..3edca96b4a2 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DebuggingInteractor.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** Interactor indicating if certain debug flows are enabled. */ +interface DebuggingInteractor { + /** This indicates that certain debug flows are enabled. */ + val debuggingEnabled: Flow + /** This indicates if udfps should instead use debug repos to supply data to its various views. */ + val udfpsEnrollmentDebuggingEnabled: Flow +} + +/** + * This interactor essentially forwards the [DebuggingRepository] + */ +class DebuggingInteractorImpl(val debuggingRepository: DebuggingRepository) : DebuggingInteractor { + override val debuggingEnabled: Flow = flow { + emit(debuggingRepository.isDebuggingEnabled()) + } + override val udfpsEnrollmentDebuggingEnabled: Flow = flow { + emit(debuggingRepository.isUdfpsEnrollmentDebuggingEnabled()) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt new file mode 100644 index 00000000000..67c00017136 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/DisplayDensityInteractor.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update + +/** + * This class is responsible for handling updates to fontScale and displayDensity and forwarding + * these events to classes that need them + */ +interface DisplayDensityInteractor { + /** Indicates the display density has been updated. */ + fun updateDisplayDensity(density: Int) + + /** Indicates the font scale has been updates. */ + fun updateFontScale(fontScale: Float) + + /** A flow that propagates fontscale. */ + val fontScale: Flow + + /** A flow that propagates displayDensity. */ + val displayDensity: Flow + + /** A flow that propagates the default display density. */ + val defaultDisplayDensity: Flow +} + +/** + * Implementation of the [DisplayDensityInteractor]. This interactor is used to forward activity + * information to the rest of the application. + */ +class DisplayDensityInteractorImpl( + currentFontScale: Float, + currentDisplayDensity: Int, + defaultDisplayDensity: Int, + scope: CoroutineScope, +) : DisplayDensityInteractor { + override fun updateDisplayDensity(density: Int) { + _displayDensity.update { density } + } + + override fun updateFontScale(fontScale: Float) { + _fontScale.update { fontScale } + } + + private val _fontScale = MutableStateFlow(currentFontScale) + private val _displayDensity = MutableStateFlow(currentDisplayDensity) + + override val fontScale: Flow = _fontScale.asStateFlow() + + override val displayDensity: Flow = _displayDensity.asStateFlow() + + override val defaultDisplayDensity: Flow = + flowOf(defaultDisplayDensity).shareIn(scope, SharingStarted.Eagerly, 1) +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt new file mode 100644 index 00000000000..2d4cb409868 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollStageInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +typealias EnrollStageThresholds = Map + +/** Interactor that provides enroll stages for enrollment. */ +interface EnrollStageInteractor { + + /** Provides enroll stages for enrollment. */ + val enrollStageThresholds: Flow +} + +class EnrollStageInteractorImpl() : EnrollStageInteractor { + override val enrollStageThresholds: Flow = + flowOf( + mapOf( + 0.0f to StageViewModel.Center, + 0.25f to StageViewModel.Guided, + 0.5f to StageViewModel.Fingertip, + 0.75f to StageViewModel.LeftEdge, + 0.875f to StageViewModel.RightEdge, + ) + ) +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt new file mode 100644 index 00000000000..e4180d337b1 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.content.Context +import android.hardware.fingerprint.FingerprintEnrollOptions +import android.hardware.fingerprint.FingerprintManager +import android.os.CancellationSignal +import android.util.Log +import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError +import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason +import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow +import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.onFailure +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.update + +/** This repository is responsible for collecting all state related to the enroll API. */ +interface FingerprintEnrollInteractor { + + /** + * By calling this function, [fingerEnrollState] will begin to be populated with data on success. + */ + suspend fun enroll( + hardwareAuthToken: ByteArray?, + enrollReason: EnrollReason, + ): Flow +} + +class FingerprintEnrollInteractorImpl( + private val applicationContext: Context, + private val fingerprintEnrollOptions: FingerprintEnrollOptions, + private val fingerprintManager: FingerprintManager, + private val fingerprintFlow: FingerprintFlow, +) : FingerprintEnrollInteractor { + private val enrollRequestOutstanding = MutableStateFlow(false) + + override suspend fun enroll( + hardwareAuthToken: ByteArray?, + enrollReason: EnrollReason, + ): Flow = callbackFlow { + // TODO (b/308456120) Improve this logic + if (enrollRequestOutstanding.value) { + Log.d(TAG, "Outstanding enroll request, waiting 150ms") + delay(150) + if (enrollRequestOutstanding.value) { + Log.e(TAG, "Request still present, continuing") + } + } + + enrollRequestOutstanding.update { true } + + var streamEnded = false + var totalSteps: Int? = null + val enrollmentCallback = + object : FingerprintManager.EnrollmentCallback() { + override fun onEnrollmentProgress(remaining: Int) { + // This is sort of an implementation detail, but unfortunately the API isn't + // very expressive. If anything we should look at changing the FingerprintManager API. + if (totalSteps == null) { + totalSteps = remaining + 1 + } + + trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error -> + Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error") + } + + if (remaining == 0) { + streamEnded = true + enrollRequestOutstanding.update { false } + } + } + + override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) { + trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error + -> + Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") + } + } + + override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) { + trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error -> + Log.d(TAG, "onEnrollmentError failed to send, due to $error") + } + Log.d(TAG, "onEnrollmentError($errMsgId)") + streamEnded = true + enrollRequestOutstanding.update { false } + } + } + + val cancellationSignal = CancellationSignal() + + fingerprintManager.enroll( + hardwareAuthToken, + cancellationSignal, + applicationContext.userId, + enrollmentCallback, + enrollReason.toOriginalReason(), + fingerprintEnrollOptions, + ) + awaitClose { + // If the stream has not been ended, and the user has stopped collecting the flow + // before it was over, send cancel. + if (!streamEnded) { + Log.e(TAG, "Cancel is sent from settings for enroll()") + cancellationSignal.cancel() + } + } + } + + companion object { + private const val TAG = "FingerprintEnrollStateRepository" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt index 652bc0c312f..ca3665ce8ca 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt @@ -18,43 +18,25 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor import android.content.Context import android.content.Intent -import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricFingerprintConstants -import android.hardware.biometrics.SensorLocationInternal -import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback import android.hardware.fingerprint.FingerprintManager.RemovalCallback -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.os.CancellationSignal import android.util.Log import com.android.settings.biometrics.GatekeeperPasswordProvider -import com.android.settings.biometrics.BiometricUtils -import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError -import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData -import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow -import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard import com.android.settings.password.ChooseLockSettingsHelper -import com.android.systemui.biometrics.shared.model.toFingerprintSensor -import com.google.android.setupcompat.util.WizardManagerHelper import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.channels.onFailure -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext @@ -66,9 +48,7 @@ class FingerprintManagerInteractorImpl( private val fingerprintManager: FingerprintManager, fingerprintSensorRepository: FingerprintSensorRepository, private val gatekeeperPasswordProvider: GatekeeperPasswordProvider, - private val pressToAuthInteractor: PressToAuthInteractor, - private val fingerprintFlow: FingerprintFlow, - private val intent: Intent, + private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor, ) : FingerprintManagerInteractor { private val maxFingerprints = @@ -77,7 +57,6 @@ class FingerprintManagerInteractorImpl( ) private val applicationContext = applicationContext.applicationContext - private val enrollRequestOutstanding = MutableStateFlow(false) override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair = suspendCoroutine { @@ -113,85 +92,8 @@ class FingerprintManagerInteractorImpl( override val maxEnrollableFingerprints = flow { emit(maxFingerprints) } - override suspend fun enroll( - hardwareAuthToken: ByteArray?, - enrollReason: EnrollReason, - ): Flow = callbackFlow { - // TODO (b/308456120) Improve this logic - if (enrollRequestOutstanding.value) { - Log.d(TAG, "Outstanding enroll request, waiting 150ms") - delay(150) - if (enrollRequestOutstanding.value) { - Log.e(TAG, "Request still present, continuing") - } - } - - enrollRequestOutstanding.update { true } - - var streamEnded = false - var totalSteps: Int? = null - val enrollmentCallback = - object : FingerprintManager.EnrollmentCallback() { - override fun onEnrollmentProgress(remaining: Int) { - // This is sort of an implementation detail, but unfortunately the API isn't - // very expressive. If anything we should look at changing the FingerprintManager API. - if (totalSteps == null) { - totalSteps = remaining + 1 - } - - trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error -> - Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error") - } - - if (remaining == 0) { - streamEnded = true - enrollRequestOutstanding.update { false } - } - } - - override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) { - trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error - -> - Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") - } - } - - override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) { - trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error -> - Log.d(TAG, "onEnrollmentError failed to send, due to $error") - } - Log.d(TAG, "onEnrollmentError($errMsgId)") - streamEnded = true - enrollRequestOutstanding.update { false } - } - } - - val cancellationSignal = CancellationSignal() - - if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) { - val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent) - intent.putExtra(BiometricUtils.EXTRA_ENROLL_REASON, - if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW else - FingerprintEnrollOptions.ENROLL_REASON_SETTINGS) - } - - fingerprintManager.enroll( - hardwareAuthToken, - cancellationSignal, - applicationContext.userId, - enrollmentCallback, - enrollReason.toOriginalReason(), - toFingerprintEnrollOptions(intent) - ) - awaitClose { - // If the stream has not been ended, and the user has stopped collecting the flow - // before it was over, send cancel. - if (!streamEnded) { - Log.e(TAG, "Cancel is sent from settings for enroll()") - cancellationSignal.cancel() - } - } - } + override suspend fun enroll(hardwareAuthToken: ByteArray?, enrollReason: EnrollReason): Flow = + fingerprintEnrollStateRepository.enroll(hardwareAuthToken, enrollReason) override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine { val callback = @@ -263,14 +165,4 @@ class FingerprintManagerInteractorImpl( ) } - private fun toFingerprintEnrollOptions(intent: Intent): FingerprintEnrollOptions { - val reason: Int = - intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) - val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder() - builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN) - if (reason != -1) { - builder.setEnrollReason(reason) - } - return builder.build() - } } diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt index 968203fff80..5d1d8c86e37 100644 --- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/OrientationInteractor.kt @@ -17,6 +17,7 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor import android.content.Context +import android.util.Log import android.view.OrientationEventListener import com.android.internal.R import kotlinx.coroutines.CoroutineScope @@ -24,16 +25,23 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transform -/** - * Interactor which provides information about orientation - */ +/** Interactor which provides information about orientation */ interface OrientationInteractor { /** A flow that contains the information about the orientation changing */ val orientation: Flow - /** A flow that contains the rotation info */ + /** + * A flow that contains the rotation info + */ val rotation: Flow + /** + * A flow that contains the rotation info matched against the def [config_reverseDefaultRotation] + */ + val rotationFromDefault: Flow /** * A Helper function that computes rotation if device is in * [R.bool.config_reverseDefaultConfigRotation] @@ -53,24 +61,11 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor } orientationEventListener.enable() awaitClose { orientationEventListener.disable() } - } + }.shareIn(activityScope, SharingStarted.Eagerly, replay = 1) - override val rotation: Flow = - callbackFlow { - val orientationEventListener = - object : OrientationEventListener(context) { - override fun onOrientationChanged(orientation: Int) { - trySend(getRotationFromDefault(context.display!!.rotation)) - } - } - orientationEventListener.enable() - awaitClose { orientationEventListener.disable() } - } - .stateIn( - activityScope, // This is tied to the activity scope - SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener - context.display!!.rotation, - ) + override val rotation: Flow = orientation.transform { emit(context.display!!.rotation) } + + override val rotationFromDefault: Flow = rotation.map { getRotationFromDefault(it) } override fun getRotationFromDefault(rotation: Int): Int { val isReverseDefaultRotation = @@ -81,4 +76,4 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor rotation } } -} \ No newline at end of file +} diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt new file mode 100644 index 00000000000..0f107cc6e36 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/VibrationInteractor.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 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.domain.interactor + +import android.content.Context +import android.os.Process +import android.os.VibrationAttributes +import android.os.VibrationEffect +import android.os.Vibrator + +/** Indicates the possible vibration effects for fingerprint enrollment */ +sealed class FingerprintVibrationEffects { + /** A vibration indicating an error */ + data object UdfpsError : FingerprintVibrationEffects() + + /** + * A vibration indicating success, this usually occurs when progress on the UDFPS enrollment has + * been made + */ + data object UdfpsSuccess : FingerprintVibrationEffects() + + /** This vibration typically occurs when a help message is shown during UDFPS enrollment */ + data object UdfpsHelp : FingerprintVibrationEffects() +} +/** Interface for sending haptic feedback */ +interface VibrationInteractor { + /** This will send a haptic vibration */ + fun vibrate(effect: FingerprintVibrationEffects, caller: String) +} + +/** Implementation of the VibrationInteractor interface */ +class VibrationInteractorImpl(val vibrator: Vibrator, val applicationContext: Context) : + VibrationInteractor { + override fun vibrate(effect: FingerprintVibrationEffects, caller: String) { + val callerString = "$caller::$effect" + val res = + when (effect) { + FingerprintVibrationEffects.UdfpsHelp, + FingerprintVibrationEffects.UdfpsError -> + Pair(VIBRATE_EFFECT_ERROR, FINGERPRINT_ENROLLING_SONIFICATION_ATTRIBUTES) + FingerprintVibrationEffects.UdfpsSuccess -> + Pair(VIBRATE_EFFECT_SUCCESS, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES) + } + vibrator.vibrate( + Process.myUid(), + applicationContext.opPackageName, + res.first, + callerString, + res.second, + ) + } + + companion object { + private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1) + private val FINGERPRINT_ENROLLING_SONIFICATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY) + private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK) + private val VIBRATE_EFFECT_SUCCESS = VibrationEffect.get(VibrationEffect.EFFECT_CLICK) + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt index 105929dd33b..d2f9f0ae014 100644 --- a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt +++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt @@ -57,8 +57,7 @@ interface FingerprintManagerInteractor { /** * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this - * enrollment. Returning the [FingerEnrollState] that represents this fingerprint enrollment - * state. + * enrollment. If successful data in the [fingerprintEnrollState] should be populated. */ suspend fun enroll( hardwareAuthToken: ByteArray?, diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt index 683397fce02..24a9a86f339 100644 --- a/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt +++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/FingerEnrollState.kt @@ -42,4 +42,16 @@ sealed class FingerEnrollState { val shouldRetryEnrollment: Boolean, val isCancelled: Boolean, ) : FingerEnrollState() + + /** Indicates an acquired event has occurred */ + data class Acquired(val acquiredGood: Boolean) : FingerEnrollState() + + /** Indicates a pointer down event has occurred */ + data object PointerDown : FingerEnrollState() + + /** Indicates a pointer up event has occurred */ + data object PointerUp : FingerEnrollState() + + /** Indicates the overlay has shown */ + data object OverlayShown : FingerEnrollState() } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/lib/model/StageViewModel.kt similarity index 94% rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt rename to src/com/android/settings/biometrics/fingerprint2/lib/model/StageViewModel.kt index 75eaec786b4..81bba157291 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/StageViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/lib/model/StageViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +package com.android.settings.biometrics.fingerprint2.lib.model /** * A view model that describes the various stages of UDFPS Enrollment. This stages typically update diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt index 70d58eab776..c8e9ca38d85 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt @@ -19,8 +19,10 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity import android.app.Activity import android.content.Intent import android.content.res.Configuration +import android.hardware.fingerprint.FingerprintEnrollOptions import android.hardware.fingerprint.FingerprintManager import android.os.Bundle +import android.os.Vibrator import android.util.Log import android.view.accessibility.AccessibilityManager import androidx.activity.result.contract.ActivityResultContracts @@ -35,21 +37,35 @@ import com.android.settings.Utils.SETTINGS_PACKAGE_NAME import com.android.settings.biometrics.BiometricEnrollBase import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED +import com.android.settings.biometrics.BiometricUtils import com.android.settings.biometrics.GatekeeperPasswordProvider +import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl +import com.android.settings.biometrics.fingerprint2.data.repository.UdfpsEnrollDebugRepositoryImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl import com.android.settings.biometrics.fingerprint2.lib.model.Default +import com.android.settings.biometrics.fingerprint2.lib.model.Settings import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment -import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.SfpsEnrollFindSensorFragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.UdfpsEnrollFindSensorFragment +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions 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.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment @@ -77,6 +93,7 @@ import com.android.settings.flags.Flags import com.android.settings.password.ChooseLockGeneric import com.android.settings.password.ChooseLockSettingsHelper import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE +import com.android.settingslib.display.DisplayDensityUtils import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.google.android.setupcompat.util.WizardManagerHelper import com.google.android.setupdesign.util.ThemeHelper @@ -95,14 +112,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { private lateinit var navigationViewModel: FingerprintNavigationViewModel private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel + private lateinit var vibrationInteractor: VibrationInteractor private lateinit var foldStateInteractor: FoldStateInteractor private lateinit var orientationInteractor: OrientationInteractor + private lateinit var displayDensityInteractor: DisplayDensityInteractor private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel private lateinit var backgroundViewModel: BackgroundViewModel private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel private lateinit var fingerprintEnrollConfirmationViewModel: FingerprintEnrollConfirmationViewModel private lateinit var udfpsViewModel: UdfpsViewModel + private lateinit var enrollStageInteractor: EnrollStageInteractor private val coroutineDispatcher = Dispatchers.Default /** Result listener for ChooseLock activity flow. */ @@ -135,6 +155,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) foldStateInteractor.onConfigurationChange(newConfig) + val displayDensityUtils = DisplayDensityUtils(applicationContext) + val currIndex = displayDensityUtils.currentIndexForDefaultDisplay + displayDensityInteractor.updateFontScale(resources.configuration.fontScale) + displayDensityInteractor.updateDisplayDensity( + displayDensityUtils.defaultDisplayDensityValues[currIndex] + ) } private fun onConfirmDevice(resultCode: Int, data: Intent?) { @@ -193,10 +219,43 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { fingerprintFlowViewModel = ViewModelProvider(this, FingerprintFlowViewModel.FingerprintFlowViewModelFactory(enrollType))[ FingerprintFlowViewModel::class.java] + val displayDensityUtils = DisplayDensityUtils(context) + val currIndex = displayDensityUtils.currentIndexForDefaultDisplay + val defaultDisplayDensity = displayDensityUtils.defaultDensityForDefaultDisplay + displayDensityInteractor = + DisplayDensityInteractorImpl( + resources.configuration.fontScale, + displayDensityUtils.defaultDisplayDensityValues[currIndex], + defaultDisplayDensity, + lifecycleScope, + ) + + val debuggingRepo = DebuggingRepositoryImpl() + val debuggingInteractor = DebuggingInteractorImpl(debuggingRepo) + val udfpsEnrollDebugRepositoryImpl = UdfpsEnrollDebugRepositoryImpl() val fingerprintSensorRepo = - FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) - val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher) + if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl + else FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) + + if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) { + val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent) + intent.putExtra( + BiometricUtils.EXTRA_ENROLL_REASON, + if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW + else FingerprintEnrollOptions.ENROLL_REASON_SETTINGS, + ) + } + + val fingerprintEnrollStateRepository = + if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl + else + FingerprintEnrollInteractorImpl( + context.applicationContext, + intent.toFingerprintEnrollOptions(), + fingerprintManager, + Settings, + ) val fingerprintManagerInteractor = FingerprintManagerInteractorImpl( @@ -205,12 +264,10 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { fingerprintManager, fingerprintSensorRepo, GatekeeperPasswordProvider(LockPatternUtils(context)), - pressToAuthInteractor, - enrollType, - getIntent(), + fingerprintEnrollStateRepository, ) - var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long? + var challenge = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long? val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token) @@ -256,6 +313,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { foldStateInteractor.onConfigurationChange(resources.configuration) orientationInteractor = OrientationInteractorImpl(context, lifecycleScope) + vibrationInteractor = + VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context) // Initialize FingerprintViewModel fingerprintEnrollViewModel = @@ -309,10 +368,23 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { ), )[RFPSViewModel::class.java] + enrollStageInteractor = EnrollStageInteractorImpl() + udfpsViewModel = ViewModelProvider( this, - UdfpsViewModel.UdfpsEnrollmentFactory(), + UdfpsViewModel.UdfpsEnrollmentFactory( + vibrationInteractor, + displayDensityInteractor, + navigationViewModel, + debuggingInteractor, + fingerprintEnrollEnrollingViewModel, + udfpsEnrollDebugRepositoryImpl, + enrollStageInteractor, + orientationInteractor, + backgroundViewModel, + fingerprintSensorRepo, + ), )[UdfpsViewModel::class.java] fingerprintEnrollConfirmationViewModel = @@ -348,7 +420,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { when (step) { Confirmation -> FingerprintEnrollConfirmationV2Fragment() is Education -> { - FingerprintEnrollFindSensorV2Fragment(step.sensor.sensorType) + when (step.sensor.sensorType) { + FingerprintSensorType.REAR -> RfpsEnrollFindSensorFragment() + FingerprintSensorType.UDFPS_OPTICAL, + FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFindSensorFragment() + else -> SfpsEnrollFindSensorFragment() + } } is Enrollment -> { when (step.sensor.sensorType) { @@ -370,7 +447,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { supportFragmentManager .beginTransaction() .setReorderingAllowed(true) - .add(R.id.fragment_container_view, theClass, null) + .add(R.id.fragment_container_view, theClass::class.java, null) .commit() navigationViewModel.update( FingerprintAction.TRANSITION_FINISHED, @@ -386,7 +463,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() { navigationViewModel.shouldFinish.filterNotNull().collect { Log.d(TAG, "FingerprintSettingsNav.finishing($it)") if (it.result != null) { - finishActivity(it.result as Int) + finishActivity(it.result) } else { finish() } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt new file mode 100644 index 00000000000..5ef17708d05 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/RfpsEnrollFindSensorFragment.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 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.fragment.education + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.android.settings.R +import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog +import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel +import com.google.android.setupcompat.template.FooterBarMixin +import com.google.android.setupcompat.template.FooterButton +import com.google.android.setupdesign.GlifLayout +import kotlinx.coroutines.launch + +/** + * A fragment that is used to educate the user about the rear fingerprint sensor on this device. + * + * The main goals of this page are + * 1. Inform the user where the fingerprint sensor is on their device + * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment] + * will work. + */ +class RfpsEnrollFindSensorFragment() : Fragment() { + /** Used for testing purposes */ + private var factory: ViewModelProvider.Factory? = null + + @VisibleForTesting + constructor(theFactory: ViewModelProvider.Factory) : this() { + factory = theFactory + } + + private val viewModelProvider: ViewModelProvider by lazy { + if (factory != null) { + ViewModelProvider(requireActivity(), factory!!) + } else { + ViewModelProvider(requireActivity()) + } + } + + private var animation: FingerprintFindSensorAnimation? = null + + private val viewModel: FingerprintEnrollFindSensorViewModel by lazy { + viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java] + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + val view = + inflater.inflate(R.layout.fingerprint_v2_enroll_find_sensor, container, false)!! as GlifLayout + view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title) + view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message) + + // Set up footer bar + val footerBarMixin = view.getMixin(FooterBarMixin::class.java) + setupSecondaryButton(footerBarMixin) + lifecycleScope.launch { + viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) } + } + + lifecycleScope.launch { + viewModel.showRfpsAnimation.collect { + animation = view.findViewById(R.id.fingerprint_sensor_location_animation) + animation!!.startAnimation() + } + } + + lifecycleScope.launch { + viewModel.showErrorDialog.collect { (errMsgId, isSetup) -> + // TODO: Covert error dialog kotlin as well + FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup) + } + } + return view + } + + override fun onDestroy() { + animation?.stopAnimation() + super.onDestroy() + } + + private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.secondaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) + .setListener { viewModel.secondaryButtonClicked() } + .setButtonType(FooterButton.ButtonType.SKIP) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) + .build() + } + + private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.primaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button) + .setListener { + Log.d(TAG, "onStartButtonClick") + viewModel.proceedToEnrolling() + } + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) + .build() + } + + companion object { + private const val TAG = "RfpsEnrollFindSensor" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt new file mode 100644 index 00000000000..584824d4573 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 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.fragment.education + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.airbnb.lottie.LottieAnimationView +import com.android.settings.R +import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel +import com.google.android.setupcompat.template.FooterBarMixin +import com.google.android.setupcompat.template.FooterButton +import com.google.android.setupdesign.GlifLayout +import kotlinx.coroutines.launch + +/** + * A fragment that is used to educate the user about the side fingerprint sensor on this device. + * + * The main goals of this page are + * 1. Inform the user where the fingerprint sensor is on their device + * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment] + * will work. + */ +class SfpsEnrollFindSensorFragment() : Fragment() { + /** Used for testing purposes */ + private var factory: ViewModelProvider.Factory? = null + + @VisibleForTesting + constructor(theFactory: ViewModelProvider.Factory) : this() { + factory = theFactory + } + + private val viewModelProvider: ViewModelProvider by lazy { + if (factory != null) { + ViewModelProvider(requireActivity(), factory!!) + } else { + ViewModelProvider(requireActivity()) + } + } + + private val viewModel: FingerprintEnrollFindSensorViewModel by lazy { + viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java] + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + val view = + inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false)!! as GlifLayout + view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) + view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message) + + // Set up footer bar + val footerBarMixin = view.getMixin(FooterBarMixin::class.java) + setupSecondaryButton(footerBarMixin) + + // Set up lottie + lifecycleScope.launch { + viewModel.sfpsLottieInfo.collect { (isFolded, rotation) -> + setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation)) + } + } + + lifecycleScope.launch { + viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) } + } + + lifecycleScope.launch { + viewModel.showErrorDialog.collect { (errMsgId, isSetup) -> + // TODO: Covert error dialog kotlin as well + FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup) + } + } + return view + } + + private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.secondaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip) + .setListener { viewModel.secondaryButtonClicked() } + .setButtonType(FooterButton.ButtonType.SKIP) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary) + .build() + } + + private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) { + footerBarMixin.primaryButton = + FooterButton.Builder(requireActivity()) + .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button) + .setListener { + Log.d(TAG, "onStartButtonClick") + viewModel.proceedToEnrolling() + } + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) + .build() + } + + private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int { + val animation: Int + when (rotation) { + Surface.ROTATION_90 -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left + else R.raw.fingerprint_edu_lottie_portrait_top_left) + Surface.ROTATION_180 -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left + else R.raw.fingerprint_edu_lottie_landscape_bottom_left) + Surface.ROTATION_270 -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right + else R.raw.fingerprint_edu_lottie_portrait_bottom_right) + else -> + animation = + (if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right + else R.raw.fingerprint_edu_lottie_landscape_top_right) + } + return animation + } + + private fun setupLottie( + view: View, + lottieAnimation: Int, + lottieClickListener: View.OnClickListener? = null, + ) { + val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie) + illustrationLottie?.setAnimation(lottieAnimation) + illustrationLottie?.playAnimation() + illustrationLottie?.setOnClickListener(lottieClickListener) + illustrationLottie?.visibility = View.VISIBLE + } + + companion object { + private const val TAG = "SfpsEnrollFindSensor" + } +} diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/UdfpsEnrollFindSensorFragment.kt similarity index 66% rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt rename to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/UdfpsEnrollFindSensorFragment.kt index 2b1ff9bf085..923a309cbac 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/UdfpsEnrollFindSensorFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment +package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education import android.os.Bundle import android.util.Log @@ -29,36 +29,27 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.lottie.LottieAnimationView import com.android.settings.R import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog -import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel -import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.google.android.setupcompat.template.FooterBarMixin import com.google.android.setupcompat.template.FooterButton import com.google.android.setupdesign.GlifLayout import kotlinx.coroutines.launch -private const val TAG = "FingerprintEnrollFindSensorV2Fragment" - /** - * A fragment that is used to educate the user about the fingerprint sensor on this device. - * - * If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for - * proceeding to the enroll enrolling. + * A fragment that is used to educate the user about the under display fingerprint sensor on this + * device. * * The main goals of this page are * 1. Inform the user where the fingerprint sensor is on their device * 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment] * will work. */ -class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorType) : Fragment() { +class UdfpsEnrollFindSensorFragment() : Fragment() { /** Used for testing purposes */ private var factory: ViewModelProvider.Factory? = null @VisibleForTesting - constructor( - sensorType: FingerprintSensorType, - theFactory: ViewModelProvider.Factory, - ) : this(sensorType) { + constructor(theFactory: ViewModelProvider.Factory) : this() { factory = theFactory } @@ -70,10 +61,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp } } - // This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie. - private var animation: FingerprintFindSensorAnimation? = null - - private var contentLayoutId: Int = -1 private val viewModel: FingerprintEnrollFindSensorViewModel by lazy { viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java] } @@ -83,31 +70,18 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp container: ViewGroup?, savedInstanceState: Bundle?, ): View? { - - 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 - } - - val view = inflater.inflate(contentLayoutId, container, false)!! as GlifLayout - setTexts(sensorType, view) + val view = + inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false)!! as GlifLayout + view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title) + view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message) // Set up footer bar val footerBarMixin = view.getMixin(FooterBarMixin::class.java) setupSecondaryButton(footerBarMixin) + lifecycleScope.launch { viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) } } - - // Set up lottie or animation - lifecycleScope.launch { - viewModel.sfpsLottieInfo.collect { (isFolded, rotation) -> - setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation)) - } - } lifecycleScope.launch { viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled -> val lottieAnimation = @@ -115,12 +89,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() } } } - lifecycleScope.launch { - viewModel.showRfpsAnimation.collect { - animation = view.findViewById(R.id.fingerprint_sensor_location_animation) - animation!!.startAnimation() - } - } lifecycleScope.launch { viewModel.showErrorDialog.collect { (errMsgId, isSetup) -> @@ -131,11 +99,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp return view } - override fun onDestroy() { - animation?.stopAnimation() - super.onDestroy() - } - private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) { footerBarMixin.secondaryButton = FooterButton.Builder(requireActivity()) @@ -159,36 +122,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp .build() } - private fun setupLottie( - view: View, - lottieAnimation: Int, - lottieClickListener: View.OnClickListener? = null, - ) { - val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie) - illustrationLottie?.setAnimation(lottieAnimation) - illustrationLottie?.playAnimation() - illustrationLottie?.setOnClickListener(lottieClickListener) - illustrationLottie?.visibility = View.VISIBLE - } - - private fun setTexts(sensorType: FingerprintSensorType?, view: GlifLayout) { - when (sensorType) { - FingerprintSensorType.UDFPS_OPTICAL, - FingerprintSensorType.UDFPS_ULTRASONIC -> { - view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title) - view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message) - } - FingerprintSensorType.POWER_BUTTON -> { - view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title) - view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message) - } - else -> { - view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title) - view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message) - } - } - } - private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int { val animation: Int when (rotation) { @@ -211,4 +144,20 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp } return animation } + + private fun setupLottie( + view: View, + lottieAnimation: Int, + lottieClickListener: View.OnClickListener? = null, + ) { + val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie) + illustrationLottie?.setAnimation(lottieAnimation) + illustrationLottie?.playAnimation() + illustrationLottie?.setOnClickListener(lottieClickListener) + illustrationLottie?.visibility = View.VISIBLE + } + + companion object { + private const val TAG = "UdfpsEnrollFindSensor" + } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.kt new file mode 100644 index 00000000000..7742fd97fc2 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/util/FingerprintOptionUtil.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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.common.util + +import android.content.Intent +import android.hardware.fingerprint.FingerprintEnrollOptions +import com.android.settings.biometrics.BiometricUtils + +fun Intent.toFingerprintEnrollOptions(): FingerprintEnrollOptions { + val reason: Int = this.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) + val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder() + builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN) + if (reason != -1) { + builder.setEnrollReason(reason) + } + return builder.build() +} diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/widget/FingerprintErrorDialog.kt similarity index 96% rename from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt rename to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/widget/FingerprintErrorDialog.kt index 9c0040b8ded..dc4842ab700 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/widget/FingerprintErrorDialog.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/common/widget/FingerprintErrorDialog.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget +package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget import android.app.AlertDialog import android.app.Dialog @@ -29,8 +29,6 @@ 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 @@ -82,6 +80,7 @@ class FingerprintErrorDialog : InstrumentedDialogFragment() { } companion object { + private const val TAG = "FingerprintErrorDialog" private const val KEY_MESSAGE = "fingerprint_message" private const val KEY_TITLE = "fingerprint_title" private const val KEY_SHOULD_TRY_AGAIN = "should_try_again" diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt index f6917f3bf0e..0ec0bdd3c26 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/fragment/RFPSEnrollFragment.kt @@ -34,9 +34,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.settings.R import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog 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 diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt index 6ee57099ea2..06450814103 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt @@ -43,7 +43,7 @@ class RFPSViewModel( orientationInteractor: OrientationInteractor, ) : ViewModel() { - private val _textViewIsVisible = MutableStateFlow(false) + private val _textViewIsVisible = MutableStateFlow(false) /** Value to indicate if the text view is visible or not */ val textViewIsVisible: Flow = _textViewIsVisible.asStateFlow() @@ -52,7 +52,7 @@ class RFPSViewModel( /** Indicates if the icon should be animating or not */ val shouldAnimateIcon = _shouldAnimateIcon - private var enrollFlow: Flow = fingerprintEnrollViewModel.enrollFLow + private var enrollFlow: Flow = fingerprintEnrollViewModel.enrollFlow /** * Enroll progress message with a replay of size 1 allowing for new subscribers to get the most @@ -142,7 +142,7 @@ class RFPSViewModel( _textViewIsVisible.update { false } _shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning /** Indicates if the icon should be animating or not */ - enrollFlow = fingerprintEnrollViewModel.enrollFLow + enrollFlow = fingerprintEnrollViewModel.enrollFlow } class RFPSViewModelFactory( diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt index d1abcd0225c..c96a1b45302 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/fragment/UdfpsEnrollFragment.kt @@ -18,6 +18,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol import android.os.Bundle import android.util.Log +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_HOVER_MOVE import android.view.View import android.view.WindowManager import android.widget.TextView @@ -30,10 +32,12 @@ import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieCompositionFactory import com.android.settings.R +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.DescriptionText -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep @@ -47,6 +51,7 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro private var factory: ViewModelProvider.Factory? = null private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] } private lateinit var udfpsEnrollView: UdfpsEnrollViewV2 + private lateinit var lottie: LottieAnimationView private val viewModelProvider: ViewModelProvider by lazy { if (factory != null) { @@ -63,7 +68,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val illustrationLottie: LottieAnimationView = view.findViewById(R.id.illustration_lottie)!! + val fragment = this + lottie = view.findViewById(R.id.illustration_lottie)!! udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!! val titleTextView = view.findViewById(R.id.title)!! val descriptionTextView = view.findViewById(R.id.description)!! @@ -79,6 +85,11 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { + launch { + viewModel.sensorLocation.collect { sensor -> + udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType) + } + } viewLifecycleOwner.lifecycleScope.launch { viewModel.headerText.collect { titleTextView.setText(it.toResource()) } } @@ -92,35 +103,59 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro } } } - viewLifecycleOwner.lifecycleScope.launch { - viewModel.sensorLocation.collect { rect -> udfpsEnrollView.setSensorRect(rect) } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.accessibilityEnabled.collect { isEnabled -> udfpsEnrollView.setAccessibilityEnabled(isEnabled) } + viewModel.shouldShowLottie.collect { + lottie.visibility = if (it) View.VISIBLE else View.GONE + } } viewLifecycleOwner.lifecycleScope.launch { viewModel.lottie.collect { lottieModel -> + if (lottie.visibility == View.GONE) { + return@collect + } val resource = lottieModel.toResource() if (resource != null) { LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp -> comp?.let { composition -> - illustrationLottie.setComposition(composition) - illustrationLottie.visibility = View.VISIBLE - illustrationLottie.playAnimation() + lottie.setComposition(composition) + lottie.visibility = View.VISIBLE + lottie.playAnimation() } } } else { - illustrationLottie.visibility = View.INVISIBLE + lottie.visibility = View.INVISIBLE } } } + viewLifecycleOwner.lifecycleScope.launch { - viewModel.udfpsEvent.collect { - Log.d(TAG, "EnrollEvent $it") - udfpsEnrollView.onUdfpsEvent(it) } + repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.accessibilityEnabled.collect { enabled -> + udfpsEnrollView.setAccessibilityEnabled(enabled) + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.enrollState.collect { + Log.d(TAG, "EnrollEvent $it") + if (it is FingerEnrollState.EnrollError) { + try { + FingerprintErrorDialog.showInstance(it, fragment) + } catch (exception: Exception) { + Log.e(TAG, "Exception occurred $exception") + } + } else { + udfpsEnrollView.onUdfpsEvent(it) + } + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) } } viewLifecycleOwner.lifecycleScope.launch { @@ -128,6 +163,15 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro } } } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.touchExplorationDebug.collect { + udfpsEnrollView.sendDebugTouchExplorationEvent( + MotionEvent.obtain(100, 100, ACTION_HOVER_MOVE, it.x.toFloat(), it.y.toFloat(), 0) + ) + } + } + viewModel.readyForEnrollment() } private fun HeaderText.toResource(): Int { diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt index 192a7871e78..175fea0ac13 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/DescriptionText.kt @@ -16,6 +16,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel + /** Represents the description text for UDFPS enrollment */ data class DescriptionText( val isSuw: Boolean, diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt index cf125c37411..a274179e303 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/EducationAnimationModel.kt @@ -16,6 +16,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel + /** Represents the lottie for UDFPS enrollment */ data class EducationAnimationModel( val isSuw: Boolean, diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt index 9cfcddc8d4d..c565f35487c 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/HeaderText.kt @@ -16,6 +16,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel + /** Represents the header text for UDFPS enrollment */ data class HeaderText( val isSuw: Boolean, diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt deleted file mode 100644 index e349cebd2eb..00000000000 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsEnrollEvent.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 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.udfps.ui.viewmodel - -/** A class indicating a udfps enroll event occurred. */ -sealed class UdfpsEnrollEvent - -/** Describes how many [remainingSteps] and how many [totalSteps] are left in udfps enrollment. */ -data class UdfpsProgress(val remainingSteps: Int, val totalSteps: Int) : UdfpsEnrollEvent() - -/** Indicates a help event has been sent by enrollment */ -data class UdfpsHelp(val helpMsgId: Int, val helpString: String) : UdfpsEnrollEvent() - -/** Indicates a error event has been sent by enrollment */ -data class UdfpsError(val errMsgId: Int, val errString: String) : UdfpsEnrollEvent() - -/** Indicates an acquired event has occurred */ -data class Acquired(val acquiredGood: Boolean) : UdfpsEnrollEvent() - -/** Indicates a pointer down event has occurred */ -data object PointerDown : UdfpsEnrollEvent() - -/** Indicates a pointer up event has occurred */ -data object PointerUp : UdfpsEnrollEvent() - -/** Indicates the overlay has shown */ -data object OverlayShown : UdfpsEnrollEvent() diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt index 7d43e1b866b..37822378f21 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt @@ -16,167 +16,284 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel -import android.graphics.Rect +import android.graphics.Point +import android.view.Surface import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository +import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository +import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects +import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor +import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay +import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch /** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */ -class UdfpsViewModel() : ViewModel() { +class UdfpsViewModel( + val vibrationInteractor: VibrationInteractor, + displayDensityInteractor: DisplayDensityInteractor, + val navigationViewModel: FingerprintNavigationViewModel, + debuggingInteractor: DebuggingInteractor, + val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel, + simulatedTouchEventsDebugRepository: SimulatedTouchEventsRepository, + enrollStageInteractor: EnrollStageInteractor, + orientationInteractor: OrientationInteractor, + backgroundViewModel: BackgroundViewModel, + sensorRepository: FingerprintSensorRepository, +) : ViewModel() { private val isSetupWizard = flowOf(false) - /** Indicates which Enrollment stage we are currently in. */ - private val sensorLocationInternal = Pair(540, 1713) - private val sensorRadius = 100 - private val sensorRect = - Rect( - this.sensorLocationInternal.first - sensorRadius, - this.sensorLocationInternal.second - sensorRadius, - this.sensorLocationInternal.first + sensorRadius, - this.sensorLocationInternal.second + sensorRadius, - ) - - private val stageThresholds = flowOf(listOf(.25, .5, .75, .875)) - - /** Indicates if accessibility is enabled */ - val accessibilityEnabled = flowOf(false) - - /** Indicates the locates of the fingerprint sensor. */ - val sensorLocation: Flow = flowOf(sensorRect) - - /** This is currently not hooked up to fingerprint manager, and is being fed mock events. */ - val udfpsEvent: Flow = - flow { - enrollEvents.forEach { events -> - events.forEach { event -> emit(event) } - delay(1000) - } - } - .flowOn(Dispatchers.IO) - - /** Determines the current [StageViewModel] enrollment is in */ - val enrollStage: Flow = - combine(stageThresholds, udfpsEvent) { thresholds, event -> - if (event is UdfpsProgress) { - thresholdToStageMap(thresholds, event.totalSteps - event.remainingSteps, event.totalSteps) + private var _enrollState: Flow = + fingerprintEnrollEnrollingViewModel.enrollFlow + /** The current state of the enrollment. */ + var enrollState: Flow = + combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) { + shouldBeRunning, + state -> + if (shouldBeRunning) { + state } else { null } } .filterNotNull() + /** + * Forwards the property sensor information. This is typically used to recreate views that must be + * aligned with the sensor. + */ + val sensorLocation = sensorRepository.fingerprintSensor + + /** Indicates if accessibility is enabled */ + val accessibilityEnabled = flowOf(true).shareIn(viewModelScope, SharingStarted.Eagerly, 1) + + init { + viewModelScope.launch { + enrollState + .combine(accessibilityEnabled) { event, isEnabled -> Pair(event, isEnabled) } + .collect { + if ( + when (it.first) { + is FingerEnrollState.EnrollError -> true + is FingerEnrollState.EnrollHelp -> it.second + is FingerEnrollState.EnrollProgress -> true + else -> false + } + ) { + vibrate(it.first) + } + } + } + + viewModelScope.launch { + backgroundViewModel.background.filter { it }.collect { didGoToBackground() } + } + } + + /** + * This is the saved progress, this is for when views are recreated and need saved state for the + * first time. + */ + var progressSaved: Flow = + enrollState + .filterIsInstance() + .filterNotNull() + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) + + /** This sends touch exploration events only used for debugging purposes. */ + val touchExplorationDebug: Flow = + debuggingInteractor.debuggingEnabled.combineTransform( + simulatedTouchEventsDebugRepository.touchExplorationDebug + ) { enabled, point -> + if (enabled) { + emit(point) + } + } + + /** Determines the current [StageViewModel] enrollment is in */ + val enrollStage: Flow = + combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event -> + if (event is FingerEnrollState.EnrollProgress) { + val progress = + (event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired + var stageToReturn: StageViewModel = StageViewModel.Center + thresholds.forEach { (threshold, stage) -> + if (progress < threshold) { + return@forEach + } + stageToReturn = stage + } + stageToReturn + } else { + null + } + } + .filterNotNull() + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) + + /** Indicates if we should show the lottie. */ + val shouldShowLottie: Flow = + combine( + displayDensityInteractor.displayDensity, + displayDensityInteractor.defaultDisplayDensity, + displayDensityInteractor.fontScale, + orientationInteractor.rotation, + ) { currDisplayDensity, defaultDisplayDensity, fontScale, rotation -> + val canShowLottieForRotation = + when (rotation) { + Surface.ROTATION_0 -> true + else -> false + } + + canShowLottieForRotation && + if (fontScale > 1.0f) { + false + } else { + defaultDisplayDensity == currDisplayDensity + } + } + .shareIn(viewModelScope, SharingStarted.Eagerly, 1) + /** The header text for UDFPS enrollment */ val headerText: Flow = combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage -> - return@combine HeaderText(isSuw, isAccessibility, stage) - } + return@combine HeaderText(isSuw, isAccessibility, stage) + } + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) private val shouldClearDescriptionText = enrollStage.map { it is StageViewModel.Unknown } /** The description text for UDFPS enrollment */ val descriptionText: Flow = combine(isSetupWizard, accessibilityEnabled, enrollStage, shouldClearDescriptionText) { - isSuw, - isAccessibility, - stage, - shouldClearText -> - if (shouldClearText) { - return@combine null - } else { - return@combine DescriptionText(isSuw, isAccessibility, stage) + isSuw, + isAccessibility, + stage, + shouldClearText -> + if (shouldClearText) { + return@combine null + } else { + return@combine DescriptionText(isSuw, isAccessibility, stage) + } } - } + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) + + /** Indicates if the consumer is ready for enrollment */ + fun readyForEnrollment() { + fingerprintEnrollEnrollingViewModel.canEnroll() + } + + /** Indicates if enrollment should stop */ + fun stopEnrollment() { + fingerprintEnrollEnrollingViewModel.stopEnroll() + } + + /** Indicates the negative button has been clicked */ + fun negativeButtonClicked() { + doReset() + navigationViewModel.update( + FingerprintAction.NEGATIVE_BUTTON_PRESSED, + navStep, + "$TAG#negativeButtonClicked", + ) + } + + /** Indicates that an enrollment was completed */ + fun finishedSuccessfully() { + doReset() + navigationViewModel.update(FingerprintAction.NEXT, navStep, "${TAG}#progressFinished") + } + + /** Indicates that the application went to the background. */ + private fun didGoToBackground() { + navigationViewModel.update( + FingerprintAction.DID_GO_TO_BACKGROUND, + navStep, + "$TAG#didGoToBackground", + ) + stopEnrollment() + } + + private fun doReset() { + /** Indicates if the icon should be animating or not */ + _enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow + } /** The lottie that should be shown for UDFPS Enrollment */ val lottie: Flow = combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage -> - return@combine EducationAnimationModel(isSuw, isAccessibility, stage) - }.distinctUntilChanged() + return@combine EducationAnimationModel(isSuw, isAccessibility, stage) + } + .distinctUntilChanged() + .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) - class UdfpsEnrollmentFactory() : ViewModelProvider.Factory { + /** Indicates we should send a vibration event */ + private fun vibrate(event: FingerEnrollState) { + val vibrationEvent = + when (event) { + is FingerEnrollState.EnrollError -> FingerprintVibrationEffects.UdfpsError + is FingerEnrollState.EnrollHelp -> FingerprintVibrationEffects.UdfpsHelp + is FingerEnrollState.EnrollProgress -> FingerprintVibrationEffects.UdfpsSuccess + else -> FingerprintVibrationEffects.UdfpsError + } + vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment") + } + + class UdfpsEnrollmentFactory( + private val vibrationInteractor: VibrationInteractor, + private val displayDensityInteractor: DisplayDensityInteractor, + private val navigationViewModel: FingerprintNavigationViewModel, + private val debuggingInteractor: DebuggingInteractor, + private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel, + private val simulatedTouchEventsRepository: SimulatedTouchEventsRepository, + private val enrollStageInteractor: EnrollStageInteractor, + private val orientationInteractor: OrientationInteractor, + private val backgroundViewModel: BackgroundViewModel, + private val sensorRepository: FingerprintSensorRepository, + ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return UdfpsViewModel() as T + return UdfpsViewModel( + vibrationInteractor, + displayDensityInteractor, + navigationViewModel, + debuggingInteractor, + fingerprintEnrollEnrollingViewModel, + simulatedTouchEventsRepository, + enrollStageInteractor, + orientationInteractor, + backgroundViewModel, + sensorRepository, + ) + as T } } companion object { private val navStep = FingerprintNavigationStep.Enrollment::class private const val TAG = "UDFPSViewModel" - private val ENROLLMENT_STAGES_ORDERED = - listOf( - StageViewModel.Center, - StageViewModel.Guided, - StageViewModel.Fingertip, - StageViewModel.LeftEdge, - StageViewModel.RightEdge, - ) - - /** - * [thresholds] is a list of 4 numbers from [0,1] that separate enrollment into 5 stages. The - * stage is determined by mapping [thresholds] * [maxSteps] and finding where the [currentStep] - * is. - * - * Each number in the array should be strictly increasing such as [0.2, 0.5, 0.6, 0.8] - */ - private fun thresholdToStageMap( - thresholds: List, - currentStep: Int, - maxSteps: Int, - ): StageViewModel { - val stageIterator = ENROLLMENT_STAGES_ORDERED.iterator() - thresholds.forEach { - val thresholdLimit = it * maxSteps - val curr = stageIterator.next() - if (currentStep < thresholdLimit) { - return curr - } - } - return stageIterator.next() - } - - /** This will be removed */ - private val enrollEvents: List> = - listOf( - listOf(OverlayShown), - listOf(UdfpsHelp(1,"hi")), - listOf(UdfpsHelp(1,"hi")), - CreateProgress(15, 16), - listOf(UdfpsHelp(1,"hi")), - CreateProgress(14, 16), - listOf(PointerDown, UdfpsHelp(1,"hi"), PointerUp), - listOf(PointerDown, UdfpsHelp(1,"hi"), PointerUp), - CreateProgress(13, 16), - CreateProgress(12, 16), - CreateProgress(11, 16), - CreateProgress(10, 16), - CreateProgress(9, 16), - CreateProgress(8, 16), - CreateProgress(7, 16), - CreateProgress(6, 16), - CreateProgress(5, 16), - CreateProgress(4, 16), - CreateProgress(3, 16), - CreateProgress(2, 16), - CreateProgress(1, 16), - CreateProgress(0, 16), - ) - - /** This will be removed */ - private fun CreateProgress(remaining: Int, total: Int): List { - return listOf(PointerDown, Acquired(true), UdfpsProgress(remaining, total), PointerUp) - } } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt index 5d4607c4f05..141924161d1 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollHelperV2.kt @@ -20,7 +20,7 @@ import android.content.Context import android.graphics.PointF import android.util.TypedValue import android.view.accessibility.AccessibilityManager -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel /** Keeps track of which guided enrollment point we should be using */ class UdfpsEnrollHelperV2(private val mContext: Context) { @@ -28,6 +28,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) { private var isGuidedEnrollment: Boolean = false private val accessibilityEnabled: Boolean private val guidedEnrollmentPoints: MutableList + /** The current index of [guidedEnrollmentPoints] for the guided enrollment. */ private var index = 0 init { @@ -76,7 +77,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) { if (accessibilityEnabled || !isGuidedEnrollment) { return null } - var scale = SCALE + val scale = SCALE val originalPoint = guidedEnrollmentPoints[index % guidedEnrollmentPoints.size] return PointF(originalPoint.x * scale, originalPoint.y * scale) } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt index 210cb2bda39..0d489954b80 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollIconV2.kt @@ -37,7 +37,7 @@ import androidx.core.animation.addListener import androidx.core.graphics.toRect import androidx.core.graphics.toRectF import com.android.settings.R -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import kotlin.math.sin /** @@ -45,6 +45,7 @@ import kotlin.math.sin * various stages of enrollment */ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() { + private var targetAnimationDuration: Long = TARGET_ANIM_DURATION_LONG private var targetAnimatorSet: AnimatorSet? = null private val movingTargetFpIcon: Drawable private val fingerprintDrawable: ShapeDrawable @@ -88,22 +89,25 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS it.recycle() } - sensorOutlinePaint = Paint(0 /* flags */).apply { - isAntiAlias = true - setColor(movingTargetFill) - style = Paint.Style.FILL - } + sensorOutlinePaint = + Paint(0 /* flags */).apply { + isAntiAlias = true + setColor(movingTargetFill) + style = Paint.Style.FILL + } - blueFill = Paint(0 /* flags */).apply { - isAntiAlias = true - setColor(movingTargetFill) - style = Paint.Style.FILL - } + blueFill = + Paint(0 /* flags */).apply { + isAntiAlias = true + setColor(movingTargetFill) + style = Paint.Style.FILL + } - movingTargetFpIcon = context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply { - setTint(enrollIconColor) - mutate() - } + movingTargetFpIcon = + context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply { + setTint(enrollIconColor) + mutate() + } fingerprintDrawable.setTint(enrollIconColor) setAlpha(255) @@ -140,7 +144,16 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS } /** Update the progress of the icon */ - fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { + fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) { + restoreAnimationTime() + // If we are restoring this view from a saved state, set animation duration to 0 to avoid + // animating progress that has already occurred. + if (isRecreating) { + setAnimationTimeToZero() + } else { + restoreAnimationTime() + } + helper.onEnrollmentProgress(remaining, totalSteps) val offset = helper.guidedEnrollmentLocation val currentBounds = getCurrLocation().toRect() @@ -149,10 +162,10 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS // offsets the initial sensor rect by a bit to get the user to move their finger a bit more. val targetRect = Rect(sensorRectBounds).toRectF() targetRect.offset(offset.x, offset.y) - var shouldAnimateMovement = + val shouldAnimateMovement = !currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f if (shouldAnimateMovement) { - targetAnimatorSet?.let { it.cancel() } + targetAnimatorSet?.cancel() animateMovement(currentBounds, targetRect, true) } } else { @@ -186,7 +199,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS val currLocation = getCurrLocation() canvas.scale(currentScale, currentScale, currLocation.centerX(), currLocation.centerY()) - sensorRectBounds?.let { canvas.drawOval(currLocation, sensorOutlinePaint) } + canvas.drawOval(currLocation, sensorOutlinePaint) fingerprintDrawable.bounds = currLocation.toRect() fingerprintDrawable.draw(canvas) } @@ -234,6 +247,19 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS } } + /** + * This sets animation time to 0. This typically happens after an activity recreation, we don't + * want to re-animate the progress/success animation with the default timer + */ + private fun setAnimationTimeToZero() { + targetAnimationDuration = 0 + } + + /** This sets animation timers back to normal, this happens after we have */ + private fun restoreAnimationTime() { + targetAnimationDuration = TARGET_ANIM_DURATION_LONG + } + companion object { private const val TAG = "UdfpsEnrollDrawableV2" private const val DEFAULT_STROKE_WIDTH = 3f @@ -242,12 +268,13 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS private fun createUdfpsIcon(context: Context): ShapeDrawable { val fpPath = context.resources.getString(R.string.config_udfpsIcon) - val drawable = ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply { - mutate() - paint.style = Paint.Style.STROKE - paint.strokeCap = Paint.Cap.ROUND - paint.strokeWidth = DEFAULT_STROKE_WIDTH - } + val drawable = + ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply { + mutate() + paint.style = Paint.Style.STROKE + paint.strokeCap = Paint.Cap.ROUND + paint.strokeWidth = DEFAULT_STROKE_WIDTH + } return drawable } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt index 5f7d2f992e5..8f0e84564a6 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollProgressBarDrawableV2.kt @@ -25,28 +25,28 @@ import android.graphics.Paint import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.drawable.Drawable -import android.os.Process -import android.os.VibrationAttributes -import android.os.VibrationEffect -import android.os.Vibrator import android.util.AttributeSet import android.util.DisplayMetrics import android.view.animation.DecelerateInterpolator import android.view.animation.Interpolator +import android.view.animation.OvershootInterpolator import androidx.annotation.ColorInt import androidx.core.animation.doOnEnd import androidx.core.graphics.toRectF import com.android.internal.annotations.VisibleForTesting import com.android.settings.R +import kotlin.math.cos import kotlin.math.max +import kotlin.math.sin /** * UDFPS enrollment progress bar. This view is responsible for drawing the progress ring and its * fill around the center of the UDFPS sensor. */ -class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: AttributeSet?) : +class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) : Drawable() { private val sensorRect: Rect = Rect() + private var rotation: Int = 0 private val strokeWidthPx: Float @ColorInt private val progressColor: Int @@ -56,7 +56,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att private val backgroundPaint: Paint @VisibleForTesting val fillPaint: Paint - private val vibrator: Vibrator private var isAccessibilityEnabled: Boolean = false private var afterFirstTouch = false private var remainingSteps = 0 @@ -64,22 +63,27 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att private var progress = 0f private var progressAnimator: ValueAnimator? = null private val progressUpdateListener: AnimatorUpdateListener - private var showingHelp = false private var fillColorAnimator: ValueAnimator? = null private val fillColorUpdateListener: AnimatorUpdateListener private var backgroundColorAnimator: ValueAnimator? = null private val backgroundColorUpdateListener: AnimatorUpdateListener - private var complete = false private var movingTargetFill = 0 private var movingTargetFillError = 0 private var enrollProgressColor = 0 private var enrollProgressHelp = 0 private var enrollProgressHelpWithTalkback = 0 private val progressBarRadius: Int + private var checkMarkDrawable: Drawable + private var checkMarkAnimator: ValueAnimator? = null + + private var fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS + private var animateArcDuration = PROGRESS_ANIMATION_DURATION_MS + private var checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS + private var checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS init { val ta = - mContext.obtainStyledAttributes( + context.obtainStyledAttributes( attrs, R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle, @@ -94,30 +98,33 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att enrollProgressHelpWithTalkback = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0) ta.recycle() - val density = mContext.resources.displayMetrics.densityDpi.toFloat() + val density = context.resources.displayMetrics.densityDpi.toFloat() strokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT) progressColor = enrollProgressColor onFirstBucketFailedColor = movingTargetFillError updateHelpColor() - backgroundPaint = Paint().apply { - strokeWidth = strokeWidthPx - setColor(movingTargetFill) - isAntiAlias = true - style = Paint.Style.STROKE - strokeCap = Paint.Cap.ROUND - } + backgroundPaint = + Paint().apply { + strokeWidth = strokeWidthPx + setColor(movingTargetFill) + isAntiAlias = true + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + + checkMarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark)!! // Progress fill should *not* use the extracted system color. - fillPaint = Paint().apply { - strokeWidth = strokeWidthPx - setColor(progressColor) - isAntiAlias = true - style = Paint.Style.STROKE - strokeCap = Paint.Cap.ROUND - } - vibrator = mContext.getSystemService(Vibrator::class.java)!! + fillPaint = + Paint().apply { + strokeWidth = strokeWidthPx + setColor(progressColor) + isAntiAlias = true + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } - progressBarRadius = mContext.resources.getInteger(R.integer.config_udfpsEnrollProgressBar) + progressBarRadius = context.resources.getInteger(R.integer.config_udfpsEnrollProgressBar) progressUpdateListener = AnimatorUpdateListener { animation: ValueAnimator -> progress = animation.getAnimatedValue() as Float @@ -134,9 +141,10 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } /** Indicates enrollment progress has occurred. */ - fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { + fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) { + afterFirstTouch = true - updateProgress(remaining, totalSteps) + updateProgress(remaining, totalSteps, isRecreating) } /** Indicates enrollment help has occurred. */ @@ -157,18 +165,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att canvas.save() // This takes the sensors bounding box and expands it by [progressBarRadius] in all directions - val sensorProgressRect = Rect(sensorRect) - sensorProgressRect.inset( - -progressBarRadius, - -progressBarRadius, - -progressBarRadius, - -progressBarRadius, - ) + val sensorProgressRect = getSensorProgressRect() // Rotate -90 degrees to make the progress start from the top right and not the bottom // right canvas.rotate( - -90f, + rotation - 90f, sensorProgressRect.centerX().toFloat(), sensorProgressRect.centerY().toFloat(), ) @@ -176,9 +178,9 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att // Draw the background color of the progress circle. canvas.drawArc( sensorProgressRect.toRectF(), - 0f /* startAngle */, - 360f /* sweepAngle */, - false /* useCenter */, + 0f, /* startAngle */ + 360f, /* sweepAngle */ + false, /* useCenter */ backgroundPaint, ) } @@ -186,13 +188,15 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att // Draw the filled portion of the progress circle. canvas.drawArc( sensorProgressRect.toRectF(), - 0f /* startAngle */, - 360f * progress /* sweepAngle */, - false /* useCenter */, + 0f, /* startAngle */ + 360f * progress, /* sweepAngle */ + false, /* useCenter */ fillPaint, ) } + canvas.restore() + checkMarkDrawable.draw(canvas) } /** Do nothing here, we will control the alpha internally. */ @@ -211,6 +215,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att */ fun drawProgressAt(sensorRect: Rect) { this.sensorRect.set(sensorRect) + invalidateSelf() } /** Indicates if accessibility is enabled or not. */ @@ -228,47 +233,21 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } } - private fun updateProgress(remainingSteps: Int, totalSteps: Int) { + private fun updateProgress(remainingSteps: Int, totalSteps: Int, isRecreating: Boolean) { if (this.remainingSteps == remainingSteps && this.totalSteps == totalSteps) { return } + + // If we are restoring this view from a saved state, set animation duration to 0 to avoid + // animating progress that has already occurred. + if (isRecreating) { + setAnimationTimeToZero() + } else { + restoreAnimationTime() + } + this.remainingSteps = remainingSteps this.totalSteps = totalSteps - if (this.showingHelp) { - if (vibrator != null && isAccessibilityEnabled) { - vibrator.vibrate( - Process.myUid(), - mContext.opPackageName, - VIBRATE_EFFECT_ERROR, - javaClass.getSimpleName() + "::onEnrollmentHelp", - FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES, - ) - } - } else { - // If the first touch is an error, remainingSteps will be -1 and the callback - // doesn't come from onEnrollmentHelp. If we are in the accessibility flow, - // we still would like to vibrate. - if (vibrator != null) { - if (remainingSteps == -1 && isAccessibilityEnabled) { - vibrator.vibrate( - Process.myUid(), - mContext.opPackageName, - VIBRATE_EFFECT_ERROR, - javaClass.getSimpleName() + "::onFirstTouchError", - FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES, - ) - } else if (remainingSteps != -1 && !isAccessibilityEnabled) { - vibrator.vibrate( - Process.myUid(), - mContext.opPackageName, - SUCCESS_VIBRATION_EFFECT, - javaClass.getSimpleName() + "::OnEnrollmentProgress", - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES, - ) - } - } - } - this.showingHelp = showingHelp this.remainingSteps = remainingSteps this.totalSteps = totalSteps val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps)) @@ -276,12 +255,69 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att if (progressAnimator != null && progressAnimator!!.isRunning) { progressAnimator!!.cancel() } + /** The [progressUpdateListener] will force re-[draw]s to occur depending on the progress. */ progressAnimator = ValueAnimator.ofFloat(progress, targetProgress).also { - it.setDuration(PROGRESS_ANIMATION_DURATION_MS) + it.setDuration(animateArcDuration) it.addUpdateListener(progressUpdateListener) it.start() } + if (remainingSteps == 0) { + runCompletionAnimation() + } + } + + private fun runCompletionAnimation() { + checkMarkAnimator?.cancel() + + checkMarkAnimator = ValueAnimator.ofFloat(0f, 1f) + checkMarkAnimator?.apply { + startDelay = checkmarkAnimationDelayDuration + setDuration(checkmarkAnimationDuration) + interpolator = OvershootInterpolator() + addUpdateListener { + val newBounds = getCheckMarkStartBounds() + val scale = it.animatedFraction + newBounds.set( + newBounds.left, + newBounds.top, + (newBounds.left + (newBounds.width() * scale)).toInt(), + (newBounds.top + (newBounds.height() * scale)).toInt(), + ) + checkMarkDrawable.bounds = newBounds + checkMarkDrawable.setVisible(true, false) + } + start() + } + } + + /** + * This returns the bounds for which the checkmark drawable should be drawn at. It should be drawn + * on the arc of the progress bar at the 315 degree mark. + */ + private fun getCheckMarkStartBounds(): Rect { + val progressBounds = getSensorProgressRect() + val radius = progressBounds.width() / 2.0 + + var x = (cos(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerX() + // Remember to negate this value as sin(>180) will return negative value + var y = (-sin(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerY() + // Subtract height|width /2 to make sure we draw in the middle of the arc. + x -= (checkMarkDrawable.intrinsicWidth / 2.0).toInt() + y -= (checkMarkDrawable.intrinsicHeight / 2.0).toInt() + + return Rect(x, y, x + checkMarkDrawable.intrinsicWidth, y + checkMarkDrawable.intrinsicHeight) + } + + private fun getSensorProgressRect(): Rect { + val sensorProgressRect = Rect(sensorRect) + sensorProgressRect.inset( + -progressBarRadius, + -progressBarRadius, + -progressBarRadius, + -progressBarRadius, + ) + return sensorProgressRect } /** @@ -294,7 +330,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } backgroundColorAnimator = ValueAnimator.ofArgb(backgroundPaint.color, onFirstBucketFailedColor).also { - it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS) + it.setDuration(fillColorAnimationDuration) it.repeatCount = 1 it.repeatMode = ValueAnimator.REVERSE it.interpolator = DEACCEL @@ -315,7 +351,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att @ColorInt val targetColor = helpColor fillColorAnimator = ValueAnimator.ofArgb(fillPaint.color, targetColor).also { - it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS) + it.setDuration(fillColorAnimationDuration) it.repeatCount = 1 it.repeatMode = ValueAnimator.REVERSE it.interpolator = DEACCEL @@ -325,33 +361,32 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att } } - private fun startCompletionAnimation() { - if (complete) { - return - } - complete = true + /** + * This sets animation time to 0. This typically happens after an activity recreation, we don't + * want to re-animate the progress/success animation with the default timer + */ + private fun setAnimationTimeToZero() { + fillColorAnimationDuration = 0 + animateArcDuration = 0 + checkmarkAnimationDelayDuration = 0 + checkmarkAnimationDuration = 0 } - private fun rollBackCompletionAnimation() { - if (!complete) { - return - } - complete = false + /** This sets animation timers back to normal, this happens after we have */ + private fun restoreAnimationTime() { + fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS + animateArcDuration = PROGRESS_ANIMATION_DURATION_MS + checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS + checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS } - private fun loadResources(context: Context, attrs: AttributeSet?) {} - companion object { private const val TAG = "UdfpsProgressBar" private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L private const val PROGRESS_ANIMATION_DURATION_MS = 400L + private const val CHECKMARK_ANIMATION_DELAY_MS = 200L + private const val CHECKMARK_ANIMATION_DURATION_MS = 300L private const val STROKE_WIDTH_DP = 12f private val DEACCEL: Interpolator = DecelerateInterpolator() - private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1) - private val FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY) - private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK) - private val SUCCESS_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_CLICK) } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt index 5af3f4bf5a9..b355f7735d7 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/widget/UdfpsEnrollViewV2.kt @@ -17,59 +17,99 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget import android.content.Context +import android.graphics.Point import android.graphics.Rect import android.util.AttributeSet import android.util.Log +import android.view.DisplayInfo +import android.view.MotionEvent +import android.view.Surface +import android.view.View +import android.view.View.OnHoverListener import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import com.android.settings.R -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.Acquired -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.OverlayShown -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.PointerDown -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.PointerUp -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsEnrollEvent -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsError -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsHelp -import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsProgress +import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState +import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel +import com.android.systemui.biometrics.UdfpsUtils +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams +import com.android.systemui.biometrics.shared.model.toInt /** * View corresponding with fingerprint_v2_udfps_enroll_view.xml. This view is responsible for * drawing the [UdfpsEnrollIconV2] and the [UdfpsEnrollProgressBarDrawableV2]. */ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { + private lateinit var fingerprintSensorType: FingerprintSensorType + private var onHoverListener: OnHoverListener = OnHoverListener { _, _ -> false } private var isAccessibilityEnabled: Boolean = false private lateinit var sensorRect: Rect private val fingerprintIcon: UdfpsEnrollIconV2 = UdfpsEnrollIconV2(mContext, attrs) private val fingerprintProgressDrawable: UdfpsEnrollProgressBarDrawableV2 = UdfpsEnrollProgressBarDrawableV2(mContext, attrs) - private var mTotalSteps = -1 - private var mRemainingSteps = -1 + private var remainingSteps = -1 + private val udfpsUtils: UdfpsUtils = UdfpsUtils() + private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer + private var isRecreating = false /** * This function computes the center (x,y) location with respect to the parent [FrameLayout] for * the [UdfpsEnrollProgressBarDrawableV2]. It also computes the [Rect] with respect to the parent - * [FrameLayout] for the [UdfpsEnrollIconV2]. + * [FrameLayout] for the [UdfpsEnrollIconV2]. This function will also setup the + * [touchExplorationAnnouncer] */ - fun setSensorRect(rect: Rect) { + fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) { this.sensorRect = rect - + this.fingerprintSensorType = sensorType findViewById(R.id.udfps_enroll_animation_fp_progress_view)?.also { it.setImageDrawable(fingerprintProgressDrawable) } findViewById(R.id.udfps_enroll_animation_fp_view)?.also { it.setImageDrawable(fingerprintIcon) } + + val rotation = display.rotation + var displayInfo = DisplayInfo() + context.display.getDisplayInfo(displayInfo) + val scaleFactor = udfpsUtils.getScaleFactor(displayInfo) + val overlayParams = + UdfpsOverlayParams( + sensorRect, + fingerprintProgressDrawable.bounds, + displayInfo.naturalWidth, + displayInfo.naturalHeight, + scaleFactor, + rotation, + sensorType.toInt(), + ) val parentView = parent as ViewGroup val coords = parentView.getLocationOnScreen() val parentLeft = coords[0] val parentTop = coords[1] val sensorRectOffset = Rect(sensorRect) + // If the view has been rotated, we need to translate the sensor coordinates + // to the new rotated view. + when (rotation) { + Surface.ROTATION_90, + Surface.ROTATION_270 -> { + sensorRectOffset.set( + sensorRectOffset.top, + sensorRectOffset.left, + sensorRectOffset.bottom, + sensorRectOffset.right, + ) + } + else -> {} + } + // Translate the sensor position into UdfpsEnrollView's view space. sensorRectOffset.offset(-parentLeft, -parentTop) fingerprintIcon.drawSensorRectAt(sensorRectOffset) fingerprintProgressDrawable.drawProgressAt(sensorRectOffset) + + touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils) } /** Updates the current enrollment stage. */ @@ -78,15 +118,17 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co } /** Receive enroll progress event */ - fun onUdfpsEvent(event: UdfpsEnrollEvent) { + fun onUdfpsEvent(event: FingerEnrollState) { when (event) { - is UdfpsProgress -> onEnrollmentProgress(event.remainingSteps, event.totalSteps) - is Acquired -> onAcquired(event.acquiredGood) - is UdfpsHelp -> onEnrollmentHelp() - is PointerDown -> onPointerDown() - is PointerUp -> onPointerUp() - OverlayShown -> overlayShown() - is UdfpsError -> udfpsError(event.errMsgId, event.errString) + is FingerEnrollState.EnrollProgress -> + onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired) + is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood) + is FingerEnrollState.EnrollHelp -> onEnrollmentHelp() + is FingerEnrollState.PointerDown -> onPointerDown() + is FingerEnrollState.PointerUp -> onPointerUp() + is FingerEnrollState.OverlayShown -> overlayShown() + is FingerEnrollState.EnrollError -> + throw IllegalArgumentException("$TAG should not handle udfps error") } } @@ -94,9 +136,37 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co fun setAccessibilityEnabled(enabled: Boolean) { this.isAccessibilityEnabled = enabled fingerprintProgressDrawable.setAccessibilityEnabled(enabled) + if (enabled) { + addHoverListener() + } else { + clearHoverListener() + } } private fun udfpsError(errMsgId: Int, errString: String) {} + /** + * Sends a touch exploration event to the [onHoverListener] this should only be used for + * debugging. + */ + fun sendDebugTouchExplorationEvent(motionEvent: MotionEvent) { + touchExplorationAnnouncer.onTouch(motionEvent) + } + + /** Sets the addHoverListener, this should happen when talkback is enabled. */ + private fun addHoverListener() { + onHoverListener = OnHoverListener { _: View, event: MotionEvent -> + sendDebugTouchExplorationEvent(event) + false + } + this.setOnHoverListener(onHoverListener) + } + + /** Clears the hover listener if one was set. */ + private fun clearHoverListener() { + val listener = OnHoverListener { _, _ -> false } + this.setOnHoverListener(listener) + onHoverListener = listener + } private fun overlayShown() { Log.e(TAG, "Implement overlayShown") @@ -115,7 +185,7 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co /** Receive onAcquired event */ private fun onAcquired(isAcquiredGood: Boolean) { - val animateIfLastStepGood = isAcquiredGood && mRemainingSteps <= 2 && mRemainingSteps >= 0 + val animateIfLastStepGood = isAcquiredGood && remainingSteps <= 2 && remainingSteps >= 0 if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired() } @@ -129,6 +199,52 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co fingerprintIcon.startDrawing() } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + // Because the layout has changed, we need to recompute all locations. + if (this::sensorRect.isInitialized && this::fingerprintSensorType.isInitialized) { + setSensorRect(sensorRect, fingerprintSensorType) + } + } + + /** + * This class is responsible for announcing touch events that are outside of the sensort rect + * area. Generally, if a touch is to the left of the sensor, the accessibility announcement will + * be something like "move right" + */ + private class TouchExplorationAnnouncer( + val context: Context, + val view: View, + val overlayParams: UdfpsOverlayParams, + val udfpsUtils: UdfpsUtils, + ) { + /** Will announce accessibility event for touches outside of the sensor rect. */ + fun onTouch(event: MotionEvent) { + val scaledTouch: Point = + udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams) + if (udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) { + return + } + val theStr: String = + udfpsUtils.onTouchOutsideOfSensorArea( + true /*touchExplorationEnabled*/, + context, + scaledTouch.x, + scaledTouch.y, + overlayParams, + ) + if (theStr != null) { + view.announceForAccessibility(theStr) + } + } + } + + /** Indicates we should should restore the views saved state. */ + fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) { + fingerprintIcon.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true) + fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true) + } + companion object { private const val TAG = "UdfpsEnrollView" } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt index 63182bb6bb9..7ddb142c01e 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModel.kt @@ -18,9 +18,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import com.android.systemui.biometrics.shared.model.FingerprintSensor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.update @@ -63,7 +65,7 @@ class FingerprintEnrollEnrollingViewModel( } /** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */ - val enrollFLow = + val enrollFlow = enrollFlowShouldBeRunning.transformLatest { if (it) { fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt index 3bf806b7eaf..ddbf1cbd777 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt @@ -25,7 +25,6 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard -import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education import com.android.systemui.biometrics.shared.model.FingerprintSensorType import kotlinx.coroutines.flow.Flow @@ -38,7 +37,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */ +/** Models the UI state for fingerprint enroll education */ class FingerprintEnrollFindSensorViewModel( private val navigationViewModel: FingerprintNavigationViewModel, private val fingerprintEnrollViewModel: FingerprintEnrollViewModel, @@ -70,7 +69,7 @@ class FingerprintEnrollFindSensorViewModel( combineTransform( _showSfpsLottie, foldStateInteractor.isFolded, - orientationInteractor.rotation, + orientationInteractor.rotationFromDefault, ) { _, isFolded, rotation -> emit(Pair(isFolded, rotation)) } @@ -147,6 +146,7 @@ class FingerprintEnrollFindSensorViewModel( } } is FingerEnrollState.EnrollHelp -> {} + else -> {} } } } diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt index bd905242fc3..7900ed725bc 100644 --- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt +++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt @@ -45,12 +45,14 @@ import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl +import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData import com.android.settings.biometrics.fingerprint2.lib.model.Settings +import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel @@ -222,6 +224,13 @@ class FingerprintSettingsV2Fragment : val fingerprintSensorProvider = FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope) val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher) + val fingerprintEnrollStateRepository = + FingerprintEnrollInteractorImpl( + requireContext().applicationContext, + intent.toFingerprintEnrollOptions(), + fingerprintManager, + Settings, + ) val interactor = FingerprintManagerInteractorImpl( @@ -230,9 +239,7 @@ class FingerprintSettingsV2Fragment : fingerprintManager, fingerprintSensorProvider, GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)), - pressToAuthInteractor, - Settings, - getIntent() + fingerprintEnrollStateRepository, ) val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN) diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt index 4d2865040cb..ebdc504c1a8 100644 --- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt +++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt @@ -91,6 +91,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) { object : OrientationInteractor { override val orientation: Flow = flowOf(Configuration.ORIENTATION_LANDSCAPE) override val rotation: Flow = flowOf(Surface.ROTATION_0) + override val rotationFromDefault: Flow = rotation override fun getRotationFromDefault(rotation: Int): Int = rotation } diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/FingerprintEnrollFindSensorScreenshotTest.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/RfpsEnrollFindSensorScreenshotTest.kt similarity index 73% rename from tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/FingerprintEnrollFindSensorScreenshotTest.kt rename to tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/RfpsEnrollFindSensorScreenshotTest.kt index 18257c2121b..594aade20bb 100644 --- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/FingerprintEnrollFindSensorScreenshotTest.kt +++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/fragment/RfpsEnrollFindSensorScreenshotTest.kt @@ -17,9 +17,7 @@ package com.android.settings.tests.screenshot.biometrics.fingerprint.fragment */ import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment -import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep -import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector +import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector.Companion.BiometricFragmentScreenShotRule import org.junit.Rule import org.junit.Test @@ -28,10 +26,7 @@ import platform.test.screenshot.FragmentScreenshotTestRule import platform.test.screenshot.ViewScreenshotTestRule.Mode @RunWith(AndroidJUnit4::class) -class FingerprintEnrollFindSensorScreenshotTest { - private val injector: Injector = - Injector(FingerprintNavigationStep.Education(Injector.interactor.sensorProp)) - +class RfpsEnrollFindSensorScreenshotTest { @Rule @JvmField var rule: FragmentScreenshotTestRule = BiometricFragmentScreenShotRule() @Test @@ -39,7 +34,7 @@ class FingerprintEnrollFindSensorScreenshotTest { rule.screenshotTest( "fp_enroll_find_sensor", Mode.MatchSize, - FingerprintEnrollFindSensorV2Fragment(injector.fingerprintSensor.sensorType, injector.factory), + RfpsEnrollFindSensorFragment(), ) } } diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt index 58d18f69db0..900afd1d55d 100644 --- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt +++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt @@ -33,8 +33,8 @@ import android.os.Handler import androidx.test.core.app.ApplicationProvider import com.android.settings.biometrics.GatekeeperPasswordProvider import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository +import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl -import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractor import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor import com.android.settings.biometrics.fingerprint2.lib.model.Default import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason @@ -82,10 +82,6 @@ class FingerprintManagerInteractorTest { @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider private var testScope = TestScope(backgroundDispatcher) - private var pressToAuthInteractor = - object : PressToAuthInteractor { - override val isEnabled = flowOf(false) - } @Before fun setup() { @@ -113,9 +109,12 @@ class FingerprintManagerInteractorTest { fingerprintManager, fingerprintSensorRepository, gateKeeperPasswordProvider, - pressToAuthInteractor, - Default, - Intent(), + FingerprintEnrollInteractorImpl( + context, + FingerprintEnrollOptions.Builder().build(), + fingerprintManager, + Default, + ), ) } diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt index 5fcd772cdcc..4906e845f87 100644 --- a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt +++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt @@ -145,7 +145,8 @@ class FingerprintEnrollFindSensorViewModelV2Test { orientationInteractor = object : OrientationInteractor { override val orientation: Flow = flowOf(Configuration.ORIENTATION_LANDSCAPE) - override val rotation: Flow = flowOf(Surface.ROTATION_0) + override val rotation: Flow = flowOf(Surface.ROTATION_0) + override val rotationFromDefault: Flow = flowOf(Surface.ROTATION_0) override fun getRotationFromDefault(rotation: Int): Int = rotation } underTest =