UDFPS Enrollment Refactor (4/N)
Accessibility + text/dpi change + rotation should be properly handled.
Debug repos were added to make UI developemnt for UDFPS much easier(not
requiring calls to fingerprint manager).
Change-Id: I89900cea0d9e953124781cdf308fb38858de5d16
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f629a36..a438fdd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2788,6 +2788,7 @@
<activity android:name=".biometrics.fingerprint2.ui.enrollment.activity.FingerprintEnrollmentV2Activity"
android:exported="true"
android:permission="android.permission.MANAGE_FINGERPRINT"
+ android:configChanges="density"
android:theme="@style/GlifTheme.Light">
<intent-filter>
<action android:name="android.settings.FINGERPRINT_SETUP" />
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 0000000..86768d6
--- /dev/null
+++ b/res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/udfps_layout"
+ style="?attr/fingerprint_layout_theme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <!-- This is used to grab style attributes and apply them
+ to this layout -->
+ <com.google.android.setupdesign.GlifLayout
+ android:id="@+id/dummy_glif_layout"
+ style="?attr/fingerprint_layout_theme"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:visibility="gone"
+ />
+
+ <LinearLayout
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/sud_layout_icon"
+ style="@style/SudGlifIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scaleType="fitStart"
+ android:src="@drawable/ic_lock" />
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/SudGlifHeaderTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:lines="2"
+ />
+
+ <TextView
+ android:id="@+id/description"
+ style="@style/SudDescription.Glif"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:lines="3"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ />
+
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/illustration_lottie"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:scaleType="centerInside"
+ android:visibility="gone"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true"
+ app:lottie_speed=".85"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/layout_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipToPadding="false"
+ >
+
+ <include layout="@layout/fingerprint_v2_udfps_enroll_view" />
+ </FrameLayout>
+
+
+</LinearLayout>
+
diff --git a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml
index ddd2c30..ab8fb2c 100644
--- a/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml
+++ b/res/layout/fingerprint_v2_udfps_enroll_enrolling.xml
@@ -18,7 +18,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/setup_wizard_layout"
+ android:id="@+id/udfps_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/DebuggingRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/DebuggingRepository.kt
new file mode 100644
index 0000000..e99f85b
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/DebuggingRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.os.Build
+
+/** Indicates if the developer has debugging features enabled. */
+interface DebuggingRepository {
+
+ /** A function that will return if a build is debuggable */
+ fun isDebuggingEnabled(): Boolean
+ /** A function that will return if udfps enrollment should be swapped with debug repos */
+ fun isUdfpsEnrollmentDebuggingEnabled(): Boolean
+}
+
+class DebuggingRepositoryImpl : DebuggingRepository {
+ /**
+ * This flag can be flipped by the engineer which should allow for certain debugging features to
+ * be enabled.
+ */
+ private val isBuildDebuggable = Build.IS_DEBUGGABLE
+ /** This flag indicates if udfps should use debug repos to supply data to its various views. */
+ private val udfpsEnrollmentDebugEnabled = true
+
+ override fun isDebuggingEnabled(): Boolean {
+ return isBuildDebuggable
+ }
+
+ override fun isUdfpsEnrollmentDebuggingEnabled(): Boolean {
+ return isDebuggingEnabled() && udfpsEnrollmentDebugEnabled
+ }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt
new file mode 100644
index 0000000..a3bcb12
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.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.data.repository
+
+import android.graphics.Point
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * This repository simulates touch events. This is mainly used to debug accessibility and ensure
+ * that talkback is correct.
+ */
+interface SimulatedTouchEventsRepository {
+ /**
+ * A flow simulating user touches.
+ */
+ val touchExplorationDebug: Flow<Point>
+}
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 0000000..9b74813
--- /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<Point> = 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<FingerprintSensor> = 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 0000000..3edca96
--- /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<Boolean>
+ /** This indicates if udfps should instead use debug repos to supply data to its various views. */
+ val udfpsEnrollmentDebuggingEnabled: Flow<Boolean>
+}
+
+/**
+ * This interactor essentially forwards the [DebuggingRepository]
+ */
+class DebuggingInteractorImpl(val debuggingRepository: DebuggingRepository) : DebuggingInteractor {
+ override val debuggingEnabled: Flow<Boolean> = flow {
+ emit(debuggingRepository.isDebuggingEnabled())
+ }
+ override val udfpsEnrollmentDebuggingEnabled: Flow<Boolean> = 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 0000000..67c0001
--- /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<Float>
+
+ /** A flow that propagates displayDensity. */
+ val displayDensity: Flow<Int>
+
+ /** A flow that propagates the default display density. */
+ val defaultDisplayDensity: Flow<Int>
+}
+
+/**
+ * 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<Float> = _fontScale.asStateFlow()
+
+ override val displayDensity: Flow<Int> = _displayDensity.asStateFlow()
+
+ override val defaultDisplayDensity: Flow<Int> =
+ 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 0000000..2d4cb40
--- /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<Float, StageViewModel>
+
+/** Interactor that provides enroll stages for enrollment. */
+interface EnrollStageInteractor {
+
+ /** Provides enroll stages for enrollment. */
+ val enrollStageThresholds: Flow<EnrollStageThresholds>
+}
+
+class EnrollStageInteractorImpl() : EnrollStageInteractor {
+ override val enrollStageThresholds: Flow<EnrollStageThresholds> =
+ 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 0000000..e4180d3
--- /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<FingerEnrollState>
+}
+
+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<FingerEnrollState> = callbackFlow {
+ // TODO (b/308456120) Improve this logic
+ if (enrollRequestOutstanding.value) {
+ Log.d(TAG, "Outstanding enroll request, waiting 150ms")
+ delay(150)
+ if (enrollRequestOutstanding.value) {
+ Log.e(TAG, "Request still present, continuing")
+ }
+ }
+
+ enrollRequestOutstanding.update { true }
+
+ var streamEnded = false
+ var totalSteps: Int? = null
+ val enrollmentCallback =
+ object : FingerprintManager.EnrollmentCallback() {
+ override fun onEnrollmentProgress(remaining: Int) {
+ // 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 652bc0c..ca3665c 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 @@
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 @@
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 @@
)
private val applicationContext = applicationContext.applicationContext
- private val enrollRequestOutstanding = MutableStateFlow(false)
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
suspendCoroutine {
@@ -113,85 +92,8 @@
override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
- override suspend fun enroll(
- hardwareAuthToken: ByteArray?,
- enrollReason: EnrollReason,
- ): Flow<FingerEnrollState> = callbackFlow {
- // TODO (b/308456120) Improve this logic
- if (enrollRequestOutstanding.value) {
- Log.d(TAG, "Outstanding enroll request, waiting 150ms")
- delay(150)
- if (enrollRequestOutstanding.value) {
- Log.e(TAG, "Request still present, continuing")
- }
- }
-
- enrollRequestOutstanding.update { true }
-
- var streamEnded = false
- var totalSteps: Int? = null
- val enrollmentCallback =
- object : FingerprintManager.EnrollmentCallback() {
- override fun onEnrollmentProgress(remaining: Int) {
- // 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<FingerEnrollState> =
+ fingerprintEnrollStateRepository.enroll(hardwareAuthToken, enrollReason)
override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
val callback =
@@ -263,14 +165,4 @@
)
}
- 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 968203f..5d1d8c8 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,17 +25,24 @@
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<Int>
- /** A flow that contains the rotation info */
+ /**
+ * A flow that contains the rotation info
+ */
val rotation: Flow<Int>
/**
+ * A flow that contains the rotation info matched against the def [config_reverseDefaultRotation]
+ */
+ val rotationFromDefault: Flow<Int>
+ /**
* A Helper function that computes rotation if device is in
* [R.bool.config_reverseDefaultConfigRotation]
*/
@@ -53,24 +61,11 @@
}
orientationEventListener.enable()
awaitClose { orientationEventListener.disable() }
- }
+ }.shareIn(activityScope, SharingStarted.Eagerly, replay = 1)
- override val rotation: Flow<Int> =
- 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<Int> = orientation.transform { emit(context.display!!.rotation) }
+
+ override val rotationFromDefault: Flow<Int> = rotation.map { getRotationFromDefault(it) }
override fun getRotationFromDefault(rotation: Int): Int {
val isReverseDefaultRotation =
@@ -81,4 +76,4 @@
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 0000000..0f107cc
--- /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 105929d..d2f9f0a 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 @@
/**
* 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 683397f..24a9a86 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 @@
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 75eaec7..81bba15 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 70d58ea..c8e9ca3 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 @@
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.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.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 @@
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 @@
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 @@
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 @@
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 @@
foldStateInteractor.onConfigurationChange(resources.configuration)
orientationInteractor = OrientationInteractorImpl(context, lifecycleScope)
+ vibrationInteractor =
+ VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context)
// Initialize FingerprintViewModel
fingerprintEnrollViewModel =
@@ -309,10 +368,23 @@
),
)[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 @@
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 @@
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 @@
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 0000000..5ef1770
--- /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/FingerprintEnrollFindSensorV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt
similarity index 64%
copy from src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
copy to src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.kt
index 2b1ff9b..584824d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/FingerprintEnrollFindSensorV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/fragment/education/SfpsEnrollFindSensorFragment.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,26 @@
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 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 FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorType) : Fragment() {
+class SfpsEnrollFindSensorFragment() : 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 +60,6 @@
}
}
- // 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,43 +69,24 @@
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.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)
- lifecycleScope.launch {
- viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
- }
- // Set up lottie or animation
+ // Set up lottie
lifecycleScope.launch {
viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
}
}
+
lifecycleScope.launch {
- viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled ->
- val lottieAnimation =
- if (isAccessibilityEnabled) R.raw.udfps_edu_a11y_lottie else R.raw.udfps_edu_lottie
- setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
- }
- }
- lifecycleScope.launch {
- viewModel.showRfpsAnimation.collect {
- animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
- animation!!.startAnimation()
- }
+ viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
}
lifecycleScope.launch {
@@ -131,11 +98,6 @@
return view
}
- override fun onDestroy() {
- animation?.stopAnimation()
- super.onDestroy()
- }
-
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
footerBarMixin.secondaryButton =
FooterButton.Builder(requireActivity())
@@ -159,36 +121,6 @@
.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 +143,20 @@
}
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 2b1ff9b..923a309 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 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 @@
}
}
- // 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 @@
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 @@
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 @@
return view
}
- override fun onDestroy() {
- animation?.stopAnimation()
- super.onDestroy()
- }
-
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
footerBarMixin.secondaryButton =
FooterButton.Builder(requireActivity())
@@ -159,36 +122,6 @@
.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 @@
}
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 0000000..7742fd9
--- /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 9c0040b..dc4842a 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 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 @@
}
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 f6917f3..0ec0bdd 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.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 6ee5709..0645081 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 @@
orientationInteractor: OrientationInteractor,
) : ViewModel() {
- private val _textViewIsVisible = MutableStateFlow<Boolean>(false)
+ private val _textViewIsVisible = MutableStateFlow(false)
/** Value to indicate if the text view is visible or not */
val textViewIsVisible: Flow<Boolean> = _textViewIsVisible.asStateFlow()
@@ -52,7 +52,7 @@
/** Indicates if the icon should be animating or not */
val shouldAnimateIcon = _shouldAnimateIcon
- private var enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFLow
+ private var enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFlow
/**
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
@@ -142,7 +142,7 @@
_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 d1abcd0..c96a1b4 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 @@
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 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 @@
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 @@
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<TextView>(R.id.title)!!
val descriptionTextView = view.findViewById<TextView>(R.id.description)!!
@@ -79,6 +85,11 @@
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 @@
}
}
}
-
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 @@
}
}
}
+
+ 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 192a787..175fea0 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 cf125c3..a274179 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 9cfcddc..c565f35 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 e349ceb..0000000
--- 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 7d43e1b..3782237 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<Rect> = flowOf(sensorRect)
-
- /** This is currently not hooked up to fingerprint manager, and is being fed mock events. */
- val udfpsEvent: Flow<UdfpsEnrollEvent> =
- flow {
- enrollEvents.forEach { events ->
- events.forEach { event -> emit(event) }
- delay(1000)
- }
- }
- .flowOn(Dispatchers.IO)
-
- /** Determines the current [StageViewModel] enrollment is in */
- val enrollStage: Flow<StageViewModel> =
- combine(stageThresholds, udfpsEvent) { thresholds, event ->
- if (event is UdfpsProgress) {
- thresholdToStageMap(thresholds, event.totalSteps - event.remainingSteps, event.totalSteps)
+ private var _enrollState: Flow<FingerEnrollState?> =
+ fingerprintEnrollEnrollingViewModel.enrollFlow
+ /** The current state of the enrollment. */
+ var enrollState: Flow<FingerEnrollState> =
+ 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<FingerEnrollState.EnrollProgress> =
+ enrollState
+ .filterIsInstance<FingerEnrollState.EnrollProgress>()
+ .filterNotNull()
+ .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
+
+ /** This sends touch exploration events only used for debugging purposes. */
+ val touchExplorationDebug: Flow<Point> =
+ debuggingInteractor.debuggingEnabled.combineTransform(
+ simulatedTouchEventsDebugRepository.touchExplorationDebug
+ ) { enabled, point ->
+ if (enabled) {
+ emit(point)
+ }
+ }
+
+ /** Determines the current [StageViewModel] enrollment is in */
+ val enrollStage: Flow<StageViewModel> =
+ 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<Boolean> =
+ 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<HeaderText> =
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<DescriptionText?> =
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<EducationAnimationModel> =
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 <T : ViewModel> create(modelClass: Class<T>): 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<Double>,
- 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<List<UdfpsEnrollEvent>> =
- 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<UdfpsEnrollEvent> {
- 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 5d4607c4..1419241 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.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 @@
private var isGuidedEnrollment: Boolean = false
private val accessibilityEnabled: Boolean
private val guidedEnrollmentPoints: MutableList<PointF>
+ /** The current index of [guidedEnrollmentPoints] for the guided enrollment. */
private var index = 0
init {
@@ -76,7 +77,7 @@
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 210cb2b..0d48995 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.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 @@
* 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 @@
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 @@
}
/** 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 @@
// 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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 5f7d2f9..8f0e845 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.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 @@
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 @@
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 @@
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 @@
}
/** 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 @@
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 @@
// 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 @@
// 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 @@
*/
fun drawProgressAt(sensorRect: Rect) {
this.sensorRect.set(sensorRect)
+ invalidateSelf()
}
/** Indicates if accessibility is enabled or not. */
@@ -228,47 +233,21 @@
}
}
- 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 @@
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 @@
}
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 @@
@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 @@
}
}
- 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 5af3f4b..b355f77 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<ImageView?>(R.id.udfps_enroll_animation_fp_progress_view)?.also {
it.setImageDrawable(fingerprintProgressDrawable)
}
findViewById<ImageView>(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 @@
}
/** 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 @@
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 @@
/** 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 @@
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 63182bb..7ddb142 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 @@
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 @@
}
/** 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 3bf806b..ddbf1cb 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.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.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 @@
combineTransform(
_showSfpsLottie,
foldStateInteractor.isFolded,
- orientationInteractor.rotation,
+ orientationInteractor.rotationFromDefault,
) { _, isFolded, rotation ->
emit(Pair(isFolded, rotation))
}
@@ -147,6 +146,7 @@
}
}
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 bd90524..7900ed7 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.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 @@
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 @@
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 4d28650..ebdc504 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 @@
object : OrientationInteractor {
override val orientation: Flow<Int> = flowOf(Configuration.ORIENTATION_LANDSCAPE)
override val rotation: Flow<Int> = flowOf(Surface.ROTATION_0)
+ override val rotationFromDefault: Flow<Int> = 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 18257c2..594aade 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 @@
*/
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.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 @@
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 58d18f6..900afd1 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 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 @@
@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 @@
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 5fcd772..4906e84 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 @@
orientationInteractor =
object : OrientationInteractor {
override val orientation: Flow<Int> = flowOf(Configuration.ORIENTATION_LANDSCAPE)
- override val rotation: Flow<Int> = flowOf(Surface.ROTATION_0)
+ override val rotation: Flow<Int> = flowOf(Surface.ROTATION_0)
+ override val rotationFromDefault: Flow<Int> = flowOf(Surface.ROTATION_0)
override fun getRotationFromDefault(rotation: Int): Int = rotation
}
underTest =