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
This commit is contained in:
@@ -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" />
|
||||
|
100
res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
Normal file
100
res/layout-land/fingerprint_v2_udfps_enroll_enrolling.xml
Normal file
@@ -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>
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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>
|
||||
}
|
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
@@ -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())
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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,
|
||||
)
|
||||
)
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -18,43 +18,25 @@ package com.android.settings.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricFingerprintConstants
|
||||
import android.hardware.biometrics.SensorLocationInternal
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions;
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
|
||||
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import android.os.CancellationSignal
|
||||
import android.util.Log
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.BiometricUtils
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
|
||||
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.onFailure
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -66,9 +48,7 @@ class FingerprintManagerInteractorImpl(
|
||||
private val fingerprintManager: FingerprintManager,
|
||||
fingerprintSensorRepository: FingerprintSensorRepository,
|
||||
private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
|
||||
private val pressToAuthInteractor: PressToAuthInteractor,
|
||||
private val fingerprintFlow: FingerprintFlow,
|
||||
private val intent: Intent,
|
||||
private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor,
|
||||
) : FingerprintManagerInteractor {
|
||||
|
||||
private val maxFingerprints =
|
||||
@@ -77,7 +57,6 @@ class FingerprintManagerInteractorImpl(
|
||||
)
|
||||
private val applicationContext = applicationContext.applicationContext
|
||||
|
||||
private val enrollRequestOutstanding = MutableStateFlow(false)
|
||||
|
||||
override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
|
||||
suspendCoroutine {
|
||||
@@ -113,85 +92,8 @@ class FingerprintManagerInteractorImpl(
|
||||
|
||||
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 @@ class FingerprintManagerInteractorImpl(
|
||||
)
|
||||
}
|
||||
|
||||
private fun toFingerprintEnrollOptions(intent: Intent): FingerprintEnrollOptions {
|
||||
val reason: Int =
|
||||
intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1)
|
||||
val builder: FingerprintEnrollOptions.Builder = FingerprintEnrollOptions.Builder()
|
||||
builder.setEnrollReason(FingerprintEnrollOptions.ENROLL_REASON_UNKNOWN)
|
||||
if (reason != -1) {
|
||||
builder.setEnrollReason(reason)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.biometrics.fingerprint2.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.OrientationEventListener
|
||||
import com.android.internal.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -24,16 +25,23 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
|
||||
/**
|
||||
* Interactor which provides information about orientation
|
||||
*/
|
||||
/** Interactor which provides information about orientation */
|
||||
interface OrientationInteractor {
|
||||
/** A flow that contains the information about the orientation changing */
|
||||
val orientation: Flow<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 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
|
||||
}
|
||||
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 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
|
||||
rotation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -57,8 +57,7 @@ interface FingerprintManagerInteractor {
|
||||
|
||||
/**
|
||||
* Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
|
||||
* enrollment. Returning the [FingerEnrollState] that represents this fingerprint enrollment
|
||||
* state.
|
||||
* enrollment. If successful data in the [fingerprintEnrollState] should be populated.
|
||||
*/
|
||||
suspend fun enroll(
|
||||
hardwareAuthToken: ByteArray?,
|
||||
|
@@ -42,4 +42,16 @@ sealed class FingerEnrollState {
|
||||
val shouldRetryEnrollment: Boolean,
|
||||
val isCancelled: Boolean,
|
||||
) : FingerEnrollState()
|
||||
|
||||
/** Indicates an acquired event has occurred */
|
||||
data class Acquired(val acquiredGood: Boolean) : FingerEnrollState()
|
||||
|
||||
/** Indicates a pointer down event has occurred */
|
||||
data object PointerDown : FingerEnrollState()
|
||||
|
||||
/** Indicates a pointer up event has occurred */
|
||||
data object PointerUp : FingerEnrollState()
|
||||
|
||||
/** Indicates the overlay has shown */
|
||||
data object OverlayShown : FingerEnrollState()
|
||||
}
|
||||
|
@@ -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
|
@@ -19,8 +19,10 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.activity
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.hardware.fingerprint.FingerprintEnrollOptions
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Bundle
|
||||
import android.os.Vibrator
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -35,21 +37,35 @@ import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
|
||||
import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||
import com.android.settings.biometrics.BiometricUtils
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.UdfpsEnrollDebugRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Default
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollConfirmationV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.SfpsEnrollFindSensorFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.UdfpsEnrollFindSensorFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
|
||||
@@ -77,6 +93,7 @@ import com.android.settings.flags.Flags
|
||||
import com.android.settings.password.ChooseLockGeneric
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
||||
import com.android.settingslib.display.DisplayDensityUtils
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper
|
||||
import com.google.android.setupdesign.util.ThemeHelper
|
||||
@@ -95,14 +112,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
private lateinit var navigationViewModel: FingerprintNavigationViewModel
|
||||
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
||||
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
||||
private lateinit var vibrationInteractor: VibrationInteractor
|
||||
private lateinit var foldStateInteractor: FoldStateInteractor
|
||||
private lateinit var orientationInteractor: OrientationInteractor
|
||||
private lateinit var displayDensityInteractor: DisplayDensityInteractor
|
||||
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||
private lateinit var backgroundViewModel: BackgroundViewModel
|
||||
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
|
||||
private lateinit var fingerprintEnrollConfirmationViewModel:
|
||||
FingerprintEnrollConfirmationViewModel
|
||||
private lateinit var udfpsViewModel: UdfpsViewModel
|
||||
private lateinit var enrollStageInteractor: EnrollStageInteractor
|
||||
private val coroutineDispatcher = Dispatchers.Default
|
||||
|
||||
/** Result listener for ChooseLock activity flow. */
|
||||
@@ -135,6 +155,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
foldStateInteractor.onConfigurationChange(newConfig)
|
||||
val displayDensityUtils = DisplayDensityUtils(applicationContext)
|
||||
val currIndex = displayDensityUtils.currentIndexForDefaultDisplay
|
||||
displayDensityInteractor.updateFontScale(resources.configuration.fontScale)
|
||||
displayDensityInteractor.updateDisplayDensity(
|
||||
displayDensityUtils.defaultDisplayDensityValues[currIndex]
|
||||
)
|
||||
}
|
||||
|
||||
private fun onConfirmDevice(resultCode: Int, data: Intent?) {
|
||||
@@ -193,10 +219,43 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
fingerprintFlowViewModel =
|
||||
ViewModelProvider(this, FingerprintFlowViewModel.FingerprintFlowViewModelFactory(enrollType))[
|
||||
FingerprintFlowViewModel::class.java]
|
||||
val displayDensityUtils = DisplayDensityUtils(context)
|
||||
val currIndex = displayDensityUtils.currentIndexForDefaultDisplay
|
||||
val defaultDisplayDensity = displayDensityUtils.defaultDensityForDefaultDisplay
|
||||
displayDensityInteractor =
|
||||
DisplayDensityInteractorImpl(
|
||||
resources.configuration.fontScale,
|
||||
displayDensityUtils.defaultDisplayDensityValues[currIndex],
|
||||
defaultDisplayDensity,
|
||||
lifecycleScope,
|
||||
)
|
||||
|
||||
val debuggingRepo = DebuggingRepositoryImpl()
|
||||
val debuggingInteractor = DebuggingInteractorImpl(debuggingRepo)
|
||||
val udfpsEnrollDebugRepositoryImpl = UdfpsEnrollDebugRepositoryImpl()
|
||||
|
||||
val fingerprintSensorRepo =
|
||||
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
|
||||
if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl
|
||||
else FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
|
||||
if (intent.getIntExtra(BiometricUtils.EXTRA_ENROLL_REASON, -1) === -1) {
|
||||
val isSuw: Boolean = WizardManagerHelper.isAnySetupWizard(intent)
|
||||
intent.putExtra(
|
||||
BiometricUtils.EXTRA_ENROLL_REASON,
|
||||
if (isSuw) FingerprintEnrollOptions.ENROLL_REASON_SUW
|
||||
else FingerprintEnrollOptions.ENROLL_REASON_SETTINGS,
|
||||
)
|
||||
}
|
||||
|
||||
val fingerprintEnrollStateRepository =
|
||||
if (debuggingRepo.isUdfpsEnrollmentDebuggingEnabled()) udfpsEnrollDebugRepositoryImpl
|
||||
else
|
||||
FingerprintEnrollInteractorImpl(
|
||||
context.applicationContext,
|
||||
intent.toFingerprintEnrollOptions(),
|
||||
fingerprintManager,
|
||||
Settings,
|
||||
)
|
||||
|
||||
val fingerprintManagerInteractor =
|
||||
FingerprintManagerInteractorImpl(
|
||||
@@ -205,12 +264,10 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
fingerprintManager,
|
||||
fingerprintSensorRepo,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context)),
|
||||
pressToAuthInteractor,
|
||||
enrollType,
|
||||
getIntent(),
|
||||
fingerprintEnrollStateRepository,
|
||||
)
|
||||
|
||||
var challenge: Long? = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||
var challenge = intent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
val gatekeeperInfo = FingerprintGatekeeperViewModel.toGateKeeperInfo(challenge, token)
|
||||
|
||||
@@ -256,6 +313,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
foldStateInteractor.onConfigurationChange(resources.configuration)
|
||||
|
||||
orientationInteractor = OrientationInteractorImpl(context, lifecycleScope)
|
||||
vibrationInteractor =
|
||||
VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context)
|
||||
|
||||
// Initialize FingerprintViewModel
|
||||
fingerprintEnrollViewModel =
|
||||
@@ -309,10 +368,23 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
),
|
||||
)[RFPSViewModel::class.java]
|
||||
|
||||
enrollStageInteractor = EnrollStageInteractorImpl()
|
||||
|
||||
udfpsViewModel =
|
||||
ViewModelProvider(
|
||||
this,
|
||||
UdfpsViewModel.UdfpsEnrollmentFactory(),
|
||||
UdfpsViewModel.UdfpsEnrollmentFactory(
|
||||
vibrationInteractor,
|
||||
displayDensityInteractor,
|
||||
navigationViewModel,
|
||||
debuggingInteractor,
|
||||
fingerprintEnrollEnrollingViewModel,
|
||||
udfpsEnrollDebugRepositoryImpl,
|
||||
enrollStageInteractor,
|
||||
orientationInteractor,
|
||||
backgroundViewModel,
|
||||
fingerprintSensorRepo,
|
||||
),
|
||||
)[UdfpsViewModel::class.java]
|
||||
|
||||
fingerprintEnrollConfirmationViewModel =
|
||||
@@ -348,7 +420,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
when (step) {
|
||||
Confirmation -> FingerprintEnrollConfirmationV2Fragment()
|
||||
is Education -> {
|
||||
FingerprintEnrollFindSensorV2Fragment(step.sensor.sensorType)
|
||||
when (step.sensor.sensorType) {
|
||||
FingerprintSensorType.REAR -> RfpsEnrollFindSensorFragment()
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFindSensorFragment()
|
||||
else -> SfpsEnrollFindSensorFragment()
|
||||
}
|
||||
}
|
||||
is Enrollment -> {
|
||||
when (step.sensor.sensorType) {
|
||||
@@ -370,7 +447,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.add(R.id.fragment_container_view, theClass, null)
|
||||
.add(R.id.fragment_container_view, theClass::class.java, null)
|
||||
.commit()
|
||||
navigationViewModel.update(
|
||||
FingerprintAction.TRANSITION_FINISHED,
|
||||
@@ -386,7 +463,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
navigationViewModel.shouldFinish.filterNotNull().collect {
|
||||
Log.d(TAG, "FingerprintSettingsNav.finishing($it)")
|
||||
if (it.result != null) {
|
||||
finishActivity(it.result as Int)
|
||||
finishActivity(it.result)
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
import com.google.android.setupdesign.GlifLayout
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A fragment that is used to educate the user about the side fingerprint sensor on this device.
|
||||
*
|
||||
* The main goals of this page are
|
||||
* 1. Inform the user where the fingerprint sensor is on their device
|
||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||
* will work.
|
||||
*/
|
||||
class SfpsEnrollFindSensorFragment() : Fragment() {
|
||||
/** Used for testing purposes */
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
||||
factory = theFactory
|
||||
}
|
||||
|
||||
private val viewModelProvider: ViewModelProvider by lazy {
|
||||
if (factory != null) {
|
||||
ViewModelProvider(requireActivity(), factory!!)
|
||||
} else {
|
||||
ViewModelProvider(requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
||||
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
val view =
|
||||
inflater.inflate(R.layout.sfps_enroll_find_sensor_layout, container, false)!! as GlifLayout
|
||||
view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
|
||||
|
||||
// Set up footer bar
|
||||
val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
|
||||
setupSecondaryButton(footerBarMixin)
|
||||
|
||||
// Set up lottie
|
||||
lifecycleScope.launch {
|
||||
viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
|
||||
setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
|
||||
// TODO: Covert error dialog kotlin as well
|
||||
FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.secondaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setListener { viewModel.secondaryButtonClicked() }
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.primaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
|
||||
.setListener {
|
||||
Log.d(TAG, "onStartButtonClick")
|
||||
viewModel.proceedToEnrolling()
|
||||
}
|
||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
|
||||
val animation: Int
|
||||
when (rotation) {
|
||||
Surface.ROTATION_90 ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left
|
||||
else R.raw.fingerprint_edu_lottie_portrait_top_left)
|
||||
Surface.ROTATION_180 ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left
|
||||
else R.raw.fingerprint_edu_lottie_landscape_bottom_left)
|
||||
Surface.ROTATION_270 ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right
|
||||
else R.raw.fingerprint_edu_lottie_portrait_bottom_right)
|
||||
else ->
|
||||
animation =
|
||||
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right
|
||||
else R.raw.fingerprint_edu_lottie_landscape_top_right)
|
||||
}
|
||||
return animation
|
||||
}
|
||||
|
||||
private fun setupLottie(
|
||||
view: View,
|
||||
lottieAnimation: Int,
|
||||
lottieClickListener: View.OnClickListener? = null,
|
||||
) {
|
||||
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||
illustrationLottie?.setAnimation(lottieAnimation)
|
||||
illustrationLottie?.playAnimation()
|
||||
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||
illustrationLottie?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SfpsEnrollFindSensor"
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
@@ -29,36 +29,27 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import com.google.android.setupcompat.template.FooterBarMixin
|
||||
import com.google.android.setupcompat.template.FooterButton
|
||||
import com.google.android.setupdesign.GlifLayout
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "FingerprintEnrollFindSensorV2Fragment"
|
||||
|
||||
/**
|
||||
* A fragment that is used to educate the user about the fingerprint sensor on this device.
|
||||
*
|
||||
* If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for
|
||||
* proceeding to the enroll enrolling.
|
||||
* A fragment that is used to educate the user about the under display fingerprint sensor on this
|
||||
* device.
|
||||
*
|
||||
* The main goals of this page are
|
||||
* 1. Inform the user where the fingerprint sensor is on their device
|
||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||
* will work.
|
||||
*/
|
||||
class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorType) : Fragment() {
|
||||
class UdfpsEnrollFindSensorFragment() : Fragment() {
|
||||
/** Used for testing purposes */
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(
|
||||
sensorType: FingerprintSensorType,
|
||||
theFactory: ViewModelProvider.Factory,
|
||||
) : this(sensorType) {
|
||||
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
||||
factory = theFactory
|
||||
}
|
||||
|
||||
@@ -70,10 +61,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
}
|
||||
}
|
||||
|
||||
// This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie.
|
||||
private var animation: FingerprintFindSensorAnimation? = null
|
||||
|
||||
private var contentLayoutId: Int = -1
|
||||
private val viewModel: FingerprintEnrollFindSensorViewModel by lazy {
|
||||
viewModelProvider[FingerprintEnrollFindSensorViewModel::class.java]
|
||||
}
|
||||
@@ -83,31 +70,18 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
|
||||
contentLayoutId =
|
||||
when (sensorType) {
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
|
||||
FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
|
||||
else -> R.layout.fingerprint_v2_enroll_find_sensor
|
||||
}
|
||||
|
||||
val view = inflater.inflate(contentLayoutId, container, false)!! as GlifLayout
|
||||
setTexts(sensorType, view)
|
||||
val view =
|
||||
inflater.inflate(R.layout.udfps_enroll_find_sensor_layout, container, false)!! as GlifLayout
|
||||
view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
|
||||
|
||||
// Set up footer bar
|
||||
val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
|
||||
setupSecondaryButton(footerBarMixin)
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
|
||||
}
|
||||
|
||||
// Set up lottie or animation
|
||||
lifecycleScope.launch {
|
||||
viewModel.sfpsLottieInfo.collect { (isFolded, rotation) ->
|
||||
setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.udfpsLottieInfo.collect { isAccessibilityEnabled ->
|
||||
val lottieAnimation =
|
||||
@@ -115,12 +89,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.showRfpsAnimation.collect {
|
||||
animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
|
||||
animation!!.startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
|
||||
@@ -131,11 +99,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
animation?.stopAnimation()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
|
||||
footerBarMixin.secondaryButton =
|
||||
FooterButton.Builder(requireActivity())
|
||||
@@ -159,36 +122,6 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun setupLottie(
|
||||
view: View,
|
||||
lottieAnimation: Int,
|
||||
lottieClickListener: View.OnClickListener? = null,
|
||||
) {
|
||||
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||
illustrationLottie?.setAnimation(lottieAnimation)
|
||||
illustrationLottie?.playAnimation()
|
||||
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||
illustrationLottie?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun setTexts(sensorType: FingerprintSensorType?, view: GlifLayout) {
|
||||
when (sensorType) {
|
||||
FingerprintSensorType.UDFPS_OPTICAL,
|
||||
FingerprintSensorType.UDFPS_ULTRASONIC -> {
|
||||
view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
|
||||
}
|
||||
FingerprintSensorType.POWER_BUTTON -> {
|
||||
view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
|
||||
}
|
||||
else -> {
|
||||
view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title)
|
||||
view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
|
||||
val animation: Int
|
||||
when (rotation) {
|
||||
@@ -211,4 +144,20 @@ class FingerprintEnrollFindSensorV2Fragment(val sensorType: FingerprintSensorTyp
|
||||
}
|
||||
return animation
|
||||
}
|
||||
|
||||
private fun setupLottie(
|
||||
view: View,
|
||||
lottieAnimation: Int,
|
||||
lottieClickListener: View.OnClickListener? = null,
|
||||
) {
|
||||
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||
illustrationLottie?.setAnimation(lottieAnimation)
|
||||
illustrationLottie?.playAnimation()
|
||||
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||
illustrationLottie?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsEnrollFindSensor"
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
@@ -29,8 +29,6 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
private const val TAG = "FingerprintErrorDialog"
|
||||
|
||||
/** A Dialog used for fingerprint enrollment when an error occurs. */
|
||||
class FingerprintErrorDialog : InstrumentedDialogFragment() {
|
||||
private lateinit var onContinue: DialogInterface.OnClickListener
|
||||
@@ -82,6 +80,7 @@ class FingerprintErrorDialog : InstrumentedDialogFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FingerprintErrorDialog"
|
||||
private const val KEY_MESSAGE = "fingerprint_message"
|
||||
private const val KEY_TITLE = "fingerprint_title"
|
||||
private const val KEY_SHOULD_TRY_AGAIN = "should_try_again"
|
@@ -34,9 +34,9 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.IconTouchDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
|
||||
|
@@ -43,7 +43,7 @@ class RFPSViewModel(
|
||||
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 @@ class RFPSViewModel(
|
||||
/** 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 @@ class RFPSViewModel(
|
||||
_textViewIsVisible.update { false }
|
||||
_shouldAnimateIcon = fingerprintEnrollViewModel.enrollFlowShouldBeRunning
|
||||
/** Indicates if the icon should be animating or not */
|
||||
enrollFlow = fingerprintEnrollViewModel.enrollFLow
|
||||
enrollFlow = fingerprintEnrollViewModel.enrollFlow
|
||||
}
|
||||
|
||||
class RFPSViewModelFactory(
|
||||
|
@@ -18,6 +18,8 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.ACTION_HOVER_MOVE
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
@@ -30,10 +32,12 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.airbnb.lottie.LottieCompositionFactory
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.DescriptionText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||
@@ -47,6 +51,7 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
private var factory: ViewModelProvider.Factory? = null
|
||||
private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] }
|
||||
private lateinit var udfpsEnrollView: UdfpsEnrollViewV2
|
||||
private lateinit var lottie: LottieAnimationView
|
||||
|
||||
private val viewModelProvider: ViewModelProvider by lazy {
|
||||
if (factory != null) {
|
||||
@@ -63,7 +68,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val illustrationLottie: LottieAnimationView = view.findViewById(R.id.illustration_lottie)!!
|
||||
val fragment = this
|
||||
lottie = view.findViewById(R.id.illustration_lottie)!!
|
||||
udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!!
|
||||
val titleTextView = view.findViewById<TextView>(R.id.title)!!
|
||||
val descriptionTextView = view.findViewById<TextView>(R.id.description)!!
|
||||
@@ -79,6 +85,11 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
launch {
|
||||
viewModel.sensorLocation.collect { sensor ->
|
||||
udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType)
|
||||
}
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.headerText.collect { titleTextView.setText(it.toResource()) }
|
||||
}
|
||||
@@ -92,35 +103,59 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.sensorLocation.collect { rect -> udfpsEnrollView.setSensorRect(rect) }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.accessibilityEnabled.collect { isEnabled -> udfpsEnrollView.setAccessibilityEnabled(isEnabled) }
|
||||
viewModel.shouldShowLottie.collect {
|
||||
lottie.visibility = if (it) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.lottie.collect { lottieModel ->
|
||||
if (lottie.visibility == View.GONE) {
|
||||
return@collect
|
||||
}
|
||||
val resource = lottieModel.toResource()
|
||||
if (resource != null) {
|
||||
LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp ->
|
||||
comp?.let { composition ->
|
||||
illustrationLottie.setComposition(composition)
|
||||
illustrationLottie.visibility = View.VISIBLE
|
||||
illustrationLottie.playAnimation()
|
||||
lottie.setComposition(composition)
|
||||
lottie.visibility = View.VISIBLE
|
||||
lottie.playAnimation()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
illustrationLottie.visibility = View.INVISIBLE
|
||||
lottie.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.udfpsEvent.collect {
|
||||
Log.d(TAG, "EnrollEvent $it")
|
||||
udfpsEnrollView.onUdfpsEvent(it) }
|
||||
repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.accessibilityEnabled.collect { enabled ->
|
||||
udfpsEnrollView.setAccessibilityEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.enrollState.collect {
|
||||
Log.d(TAG, "EnrollEvent $it")
|
||||
if (it is FingerEnrollState.EnrollError) {
|
||||
try {
|
||||
FingerprintErrorDialog.showInstance(it, fragment)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Exception occurred $exception")
|
||||
}
|
||||
} else {
|
||||
udfpsEnrollView.onUdfpsEvent(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
@@ -128,6 +163,15 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.touchExplorationDebug.collect {
|
||||
udfpsEnrollView.sendDebugTouchExplorationEvent(
|
||||
MotionEvent.obtain(100, 100, ACTION_HOVER_MOVE, it.x.toFloat(), it.y.toFloat(), 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModel.readyForEnrollment()
|
||||
}
|
||||
|
||||
private fun HeaderText.toResource(): Int {
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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()
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.util.TypedValue
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
|
||||
/** Keeps track of which guided enrollment point we should be using */
|
||||
class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||
@@ -28,6 +28,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||
private var isGuidedEnrollment: Boolean = false
|
||||
private val accessibilityEnabled: Boolean
|
||||
private val guidedEnrollmentPoints: MutableList<PointF>
|
||||
/** The current index of [guidedEnrollmentPoints] for the guided enrollment. */
|
||||
private var index = 0
|
||||
|
||||
init {
|
||||
@@ -76,7 +77,7 @@ class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||
if (accessibilityEnabled || !isGuidedEnrollment) {
|
||||
return null
|
||||
}
|
||||
var scale = SCALE
|
||||
val scale = SCALE
|
||||
val originalPoint = guidedEnrollmentPoints[index % guidedEnrollmentPoints.size]
|
||||
return PointF(originalPoint.x * scale, originalPoint.y * scale)
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ import androidx.core.animation.addListener
|
||||
import androidx.core.graphics.toRect
|
||||
import androidx.core.graphics.toRectF
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
@@ -45,6 +45,7 @@ import kotlin.math.sin
|
||||
* various stages of enrollment
|
||||
*/
|
||||
class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() {
|
||||
private var targetAnimationDuration: Long = TARGET_ANIM_DURATION_LONG
|
||||
private var targetAnimatorSet: AnimatorSet? = null
|
||||
private val movingTargetFpIcon: Drawable
|
||||
private val fingerprintDrawable: ShapeDrawable
|
||||
@@ -88,22 +89,25 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
it.recycle()
|
||||
}
|
||||
|
||||
sensorOutlinePaint = Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
sensorOutlinePaint =
|
||||
Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
blueFill = Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
blueFill =
|
||||
Paint(0 /* flags */).apply {
|
||||
isAntiAlias = true
|
||||
setColor(movingTargetFill)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
movingTargetFpIcon = context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply {
|
||||
setTint(enrollIconColor)
|
||||
mutate()
|
||||
}
|
||||
movingTargetFpIcon =
|
||||
context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply {
|
||||
setTint(enrollIconColor)
|
||||
mutate()
|
||||
}
|
||||
|
||||
fingerprintDrawable.setTint(enrollIconColor)
|
||||
setAlpha(255)
|
||||
@@ -140,7 +144,16 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
}
|
||||
|
||||
/** Update the progress of the icon */
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
|
||||
restoreAnimationTime()
|
||||
// If we are restoring this view from a saved state, set animation duration to 0 to avoid
|
||||
// animating progress that has already occurred.
|
||||
if (isRecreating) {
|
||||
setAnimationTimeToZero()
|
||||
} else {
|
||||
restoreAnimationTime()
|
||||
}
|
||||
|
||||
helper.onEnrollmentProgress(remaining, totalSteps)
|
||||
val offset = helper.guidedEnrollmentLocation
|
||||
val currentBounds = getCurrLocation().toRect()
|
||||
@@ -149,10 +162,10 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
// offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
|
||||
val targetRect = Rect(sensorRectBounds).toRectF()
|
||||
targetRect.offset(offset.x, offset.y)
|
||||
var shouldAnimateMovement =
|
||||
val shouldAnimateMovement =
|
||||
!currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f
|
||||
if (shouldAnimateMovement) {
|
||||
targetAnimatorSet?.let { it.cancel() }
|
||||
targetAnimatorSet?.cancel()
|
||||
animateMovement(currentBounds, targetRect, true)
|
||||
}
|
||||
} else {
|
||||
@@ -186,7 +199,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
val currLocation = getCurrLocation()
|
||||
canvas.scale(currentScale, currentScale, currLocation.centerX(), currLocation.centerY())
|
||||
|
||||
sensorRectBounds?.let { canvas.drawOval(currLocation, sensorOutlinePaint) }
|
||||
canvas.drawOval(currLocation, sensorOutlinePaint)
|
||||
fingerprintDrawable.bounds = currLocation.toRect()
|
||||
fingerprintDrawable.draw(canvas)
|
||||
}
|
||||
@@ -234,6 +247,19 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets animation time to 0. This typically happens after an activity recreation, we don't
|
||||
* want to re-animate the progress/success animation with the default timer
|
||||
*/
|
||||
private fun setAnimationTimeToZero() {
|
||||
targetAnimationDuration = 0
|
||||
}
|
||||
|
||||
/** This sets animation timers back to normal, this happens after we have */
|
||||
private fun restoreAnimationTime() {
|
||||
targetAnimationDuration = TARGET_ANIM_DURATION_LONG
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsEnrollDrawableV2"
|
||||
private const val DEFAULT_STROKE_WIDTH = 3f
|
||||
@@ -242,12 +268,13 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
|
||||
private fun createUdfpsIcon(context: Context): ShapeDrawable {
|
||||
val fpPath = context.resources.getString(R.string.config_udfpsIcon)
|
||||
val drawable = ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply {
|
||||
mutate()
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeCap = Paint.Cap.ROUND
|
||||
paint.strokeWidth = DEFAULT_STROKE_WIDTH
|
||||
}
|
||||
val drawable =
|
||||
ShapeDrawable(PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)).apply {
|
||||
mutate()
|
||||
paint.style = Paint.Style.STROKE
|
||||
paint.strokeCap = Paint.Cap.ROUND
|
||||
paint.strokeWidth = DEFAULT_STROKE_WIDTH
|
||||
}
|
||||
return drawable
|
||||
}
|
||||
}
|
||||
|
@@ -25,28 +25,28 @@ import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Process
|
||||
import android.os.VibrationAttributes
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.Interpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.graphics.toRectF
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.settings.R
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* UDFPS enrollment progress bar. This view is responsible for drawing the progress ring and its
|
||||
* fill around the center of the UDFPS sensor.
|
||||
*/
|
||||
class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: AttributeSet?) :
|
||||
class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) :
|
||||
Drawable() {
|
||||
private val sensorRect: Rect = Rect()
|
||||
private var rotation: Int = 0
|
||||
private val strokeWidthPx: Float
|
||||
|
||||
@ColorInt private val progressColor: Int
|
||||
@@ -56,7 +56,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
private val backgroundPaint: Paint
|
||||
|
||||
@VisibleForTesting val fillPaint: Paint
|
||||
private val vibrator: Vibrator
|
||||
private var isAccessibilityEnabled: Boolean = false
|
||||
private var afterFirstTouch = false
|
||||
private var remainingSteps = 0
|
||||
@@ -64,22 +63,27 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
private var progress = 0f
|
||||
private var progressAnimator: ValueAnimator? = null
|
||||
private val progressUpdateListener: AnimatorUpdateListener
|
||||
private var showingHelp = false
|
||||
private var fillColorAnimator: ValueAnimator? = null
|
||||
private val fillColorUpdateListener: AnimatorUpdateListener
|
||||
private var backgroundColorAnimator: ValueAnimator? = null
|
||||
private val backgroundColorUpdateListener: AnimatorUpdateListener
|
||||
private var complete = false
|
||||
private var movingTargetFill = 0
|
||||
private var movingTargetFillError = 0
|
||||
private var enrollProgressColor = 0
|
||||
private var enrollProgressHelp = 0
|
||||
private var enrollProgressHelpWithTalkback = 0
|
||||
private val progressBarRadius: Int
|
||||
private var checkMarkDrawable: Drawable
|
||||
private var checkMarkAnimator: ValueAnimator? = null
|
||||
|
||||
private var fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS
|
||||
private var animateArcDuration = PROGRESS_ANIMATION_DURATION_MS
|
||||
private var checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS
|
||||
private var checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS
|
||||
|
||||
init {
|
||||
val ta =
|
||||
mContext.obtainStyledAttributes(
|
||||
context.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.BiometricsEnrollView,
|
||||
R.attr.biometricsEnrollStyle,
|
||||
@@ -94,30 +98,33 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
enrollProgressHelpWithTalkback =
|
||||
ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0)
|
||||
ta.recycle()
|
||||
val density = mContext.resources.displayMetrics.densityDpi.toFloat()
|
||||
val density = context.resources.displayMetrics.densityDpi.toFloat()
|
||||
strokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT)
|
||||
progressColor = enrollProgressColor
|
||||
onFirstBucketFailedColor = movingTargetFillError
|
||||
updateHelpColor()
|
||||
backgroundPaint = Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(movingTargetFill)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
backgroundPaint =
|
||||
Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(movingTargetFill)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
|
||||
checkMarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark)!!
|
||||
|
||||
// Progress fill should *not* use the extracted system color.
|
||||
fillPaint = Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(progressColor)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
vibrator = mContext.getSystemService(Vibrator::class.java)!!
|
||||
fillPaint =
|
||||
Paint().apply {
|
||||
strokeWidth = strokeWidthPx
|
||||
setColor(progressColor)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
|
||||
progressBarRadius = mContext.resources.getInteger(R.integer.config_udfpsEnrollProgressBar)
|
||||
progressBarRadius = context.resources.getInteger(R.integer.config_udfpsEnrollProgressBar)
|
||||
|
||||
progressUpdateListener = AnimatorUpdateListener { animation: ValueAnimator ->
|
||||
progress = animation.getAnimatedValue() as Float
|
||||
@@ -134,9 +141,10 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
|
||||
/** Indicates enrollment progress has occurred. */
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
|
||||
|
||||
afterFirstTouch = true
|
||||
updateProgress(remaining, totalSteps)
|
||||
updateProgress(remaining, totalSteps, isRecreating)
|
||||
}
|
||||
|
||||
/** Indicates enrollment help has occurred. */
|
||||
@@ -157,18 +165,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
|
||||
canvas.save()
|
||||
// This takes the sensors bounding box and expands it by [progressBarRadius] in all directions
|
||||
val sensorProgressRect = Rect(sensorRect)
|
||||
sensorProgressRect.inset(
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
)
|
||||
val sensorProgressRect = getSensorProgressRect()
|
||||
|
||||
// Rotate -90 degrees to make the progress start from the top right and not the bottom
|
||||
// right
|
||||
canvas.rotate(
|
||||
-90f,
|
||||
rotation - 90f,
|
||||
sensorProgressRect.centerX().toFloat(),
|
||||
sensorProgressRect.centerY().toFloat(),
|
||||
)
|
||||
@@ -176,9 +178,9 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
// Draw the background color of the progress circle.
|
||||
canvas.drawArc(
|
||||
sensorProgressRect.toRectF(),
|
||||
0f /* startAngle */,
|
||||
360f /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
0f, /* startAngle */
|
||||
360f, /* sweepAngle */
|
||||
false, /* useCenter */
|
||||
backgroundPaint,
|
||||
)
|
||||
}
|
||||
@@ -186,13 +188,15 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
// Draw the filled portion of the progress circle.
|
||||
canvas.drawArc(
|
||||
sensorProgressRect.toRectF(),
|
||||
0f /* startAngle */,
|
||||
360f * progress /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
0f, /* startAngle */
|
||||
360f * progress, /* sweepAngle */
|
||||
false, /* useCenter */
|
||||
fillPaint,
|
||||
)
|
||||
}
|
||||
|
||||
canvas.restore()
|
||||
checkMarkDrawable.draw(canvas)
|
||||
}
|
||||
|
||||
/** Do nothing here, we will control the alpha internally. */
|
||||
@@ -211,6 +215,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
*/
|
||||
fun drawProgressAt(sensorRect: Rect) {
|
||||
this.sensorRect.set(sensorRect)
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
/** Indicates if accessibility is enabled or not. */
|
||||
@@ -228,47 +233,21 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgress(remainingSteps: Int, totalSteps: Int) {
|
||||
private fun updateProgress(remainingSteps: Int, totalSteps: Int, isRecreating: Boolean) {
|
||||
if (this.remainingSteps == remainingSteps && this.totalSteps == totalSteps) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we are restoring this view from a saved state, set animation duration to 0 to avoid
|
||||
// animating progress that has already occurred.
|
||||
if (isRecreating) {
|
||||
setAnimationTimeToZero()
|
||||
} else {
|
||||
restoreAnimationTime()
|
||||
}
|
||||
|
||||
this.remainingSteps = remainingSteps
|
||||
this.totalSteps = totalSteps
|
||||
if (this.showingHelp) {
|
||||
if (vibrator != null && isAccessibilityEnabled) {
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
mContext.opPackageName,
|
||||
VIBRATE_EFFECT_ERROR,
|
||||
javaClass.getSimpleName() + "::onEnrollmentHelp",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If the first touch is an error, remainingSteps will be -1 and the callback
|
||||
// doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
|
||||
// we still would like to vibrate.
|
||||
if (vibrator != null) {
|
||||
if (remainingSteps == -1 && isAccessibilityEnabled) {
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
mContext.opPackageName,
|
||||
VIBRATE_EFFECT_ERROR,
|
||||
javaClass.getSimpleName() + "::onFirstTouchError",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES,
|
||||
)
|
||||
} else if (remainingSteps != -1 && !isAccessibilityEnabled) {
|
||||
vibrator.vibrate(
|
||||
Process.myUid(),
|
||||
mContext.opPackageName,
|
||||
SUCCESS_VIBRATION_EFFECT,
|
||||
javaClass.getSimpleName() + "::OnEnrollmentProgress",
|
||||
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.showingHelp = showingHelp
|
||||
this.remainingSteps = remainingSteps
|
||||
this.totalSteps = totalSteps
|
||||
val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps))
|
||||
@@ -276,12 +255,69 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
if (progressAnimator != null && progressAnimator!!.isRunning) {
|
||||
progressAnimator!!.cancel()
|
||||
}
|
||||
/** The [progressUpdateListener] will force re-[draw]s to occur depending on the progress. */
|
||||
progressAnimator =
|
||||
ValueAnimator.ofFloat(progress, targetProgress).also {
|
||||
it.setDuration(PROGRESS_ANIMATION_DURATION_MS)
|
||||
it.setDuration(animateArcDuration)
|
||||
it.addUpdateListener(progressUpdateListener)
|
||||
it.start()
|
||||
}
|
||||
if (remainingSteps == 0) {
|
||||
runCompletionAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCompletionAnimation() {
|
||||
checkMarkAnimator?.cancel()
|
||||
|
||||
checkMarkAnimator = ValueAnimator.ofFloat(0f, 1f)
|
||||
checkMarkAnimator?.apply {
|
||||
startDelay = checkmarkAnimationDelayDuration
|
||||
setDuration(checkmarkAnimationDuration)
|
||||
interpolator = OvershootInterpolator()
|
||||
addUpdateListener {
|
||||
val newBounds = getCheckMarkStartBounds()
|
||||
val scale = it.animatedFraction
|
||||
newBounds.set(
|
||||
newBounds.left,
|
||||
newBounds.top,
|
||||
(newBounds.left + (newBounds.width() * scale)).toInt(),
|
||||
(newBounds.top + (newBounds.height() * scale)).toInt(),
|
||||
)
|
||||
checkMarkDrawable.bounds = newBounds
|
||||
checkMarkDrawable.setVisible(true, false)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the bounds for which the checkmark drawable should be drawn at. It should be drawn
|
||||
* on the arc of the progress bar at the 315 degree mark.
|
||||
*/
|
||||
private fun getCheckMarkStartBounds(): Rect {
|
||||
val progressBounds = getSensorProgressRect()
|
||||
val radius = progressBounds.width() / 2.0
|
||||
|
||||
var x = (cos(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerX()
|
||||
// Remember to negate this value as sin(>180) will return negative value
|
||||
var y = (-sin(Math.toRadians(315.0)) * radius).toInt() + progressBounds.centerY()
|
||||
// Subtract height|width /2 to make sure we draw in the middle of the arc.
|
||||
x -= (checkMarkDrawable.intrinsicWidth / 2.0).toInt()
|
||||
y -= (checkMarkDrawable.intrinsicHeight / 2.0).toInt()
|
||||
|
||||
return Rect(x, y, x + checkMarkDrawable.intrinsicWidth, y + checkMarkDrawable.intrinsicHeight)
|
||||
}
|
||||
|
||||
private fun getSensorProgressRect(): Rect {
|
||||
val sensorProgressRect = Rect(sensorRect)
|
||||
sensorProgressRect.inset(
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
-progressBarRadius,
|
||||
)
|
||||
return sensorProgressRect
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +330,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
backgroundColorAnimator =
|
||||
ValueAnimator.ofArgb(backgroundPaint.color, onFirstBucketFailedColor).also {
|
||||
it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS)
|
||||
it.setDuration(fillColorAnimationDuration)
|
||||
it.repeatCount = 1
|
||||
it.repeatMode = ValueAnimator.REVERSE
|
||||
it.interpolator = DEACCEL
|
||||
@@ -315,7 +351,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
@ColorInt val targetColor = helpColor
|
||||
fillColorAnimator =
|
||||
ValueAnimator.ofArgb(fillPaint.color, targetColor).also {
|
||||
it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS)
|
||||
it.setDuration(fillColorAnimationDuration)
|
||||
it.repeatCount = 1
|
||||
it.repeatMode = ValueAnimator.REVERSE
|
||||
it.interpolator = DEACCEL
|
||||
@@ -325,33 +361,32 @@ class UdfpsEnrollProgressBarDrawableV2(private val mContext: Context, attrs: Att
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCompletionAnimation() {
|
||||
if (complete) {
|
||||
return
|
||||
}
|
||||
complete = true
|
||||
/**
|
||||
* This sets animation time to 0. This typically happens after an activity recreation, we don't
|
||||
* want to re-animate the progress/success animation with the default timer
|
||||
*/
|
||||
private fun setAnimationTimeToZero() {
|
||||
fillColorAnimationDuration = 0
|
||||
animateArcDuration = 0
|
||||
checkmarkAnimationDelayDuration = 0
|
||||
checkmarkAnimationDuration = 0
|
||||
}
|
||||
|
||||
private fun rollBackCompletionAnimation() {
|
||||
if (!complete) {
|
||||
return
|
||||
}
|
||||
complete = false
|
||||
/** This sets animation timers back to normal, this happens after we have */
|
||||
private fun restoreAnimationTime() {
|
||||
fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS
|
||||
animateArcDuration = PROGRESS_ANIMATION_DURATION_MS
|
||||
checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS
|
||||
checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS
|
||||
}
|
||||
|
||||
private fun loadResources(context: Context, attrs: AttributeSet?) {}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsProgressBar"
|
||||
private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L
|
||||
private const val PROGRESS_ANIMATION_DURATION_MS = 400L
|
||||
private const val CHECKMARK_ANIMATION_DELAY_MS = 200L
|
||||
private const val CHECKMARK_ANIMATION_DURATION_MS = 300L
|
||||
private const val STROKE_WIDTH_DP = 12f
|
||||
private val DEACCEL: Interpolator = DecelerateInterpolator()
|
||||
private val VIBRATE_EFFECT_ERROR = VibrationEffect.createWaveform(longArrayOf(0, 5, 55, 60), -1)
|
||||
private val FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY)
|
||||
private val HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
|
||||
private val SUCCESS_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
|
||||
}
|
||||
}
|
||||
|
@@ -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 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
}
|
||||
|
||||
/** Receive enroll progress event */
|
||||
fun onUdfpsEvent(event: UdfpsEnrollEvent) {
|
||||
fun onUdfpsEvent(event: FingerEnrollState) {
|
||||
when (event) {
|
||||
is UdfpsProgress -> onEnrollmentProgress(event.remainingSteps, event.totalSteps)
|
||||
is Acquired -> onAcquired(event.acquiredGood)
|
||||
is UdfpsHelp -> onEnrollmentHelp()
|
||||
is PointerDown -> onPointerDown()
|
||||
is PointerUp -> onPointerUp()
|
||||
OverlayShown -> overlayShown()
|
||||
is UdfpsError -> udfpsError(event.errMsgId, event.errString)
|
||||
is FingerEnrollState.EnrollProgress ->
|
||||
onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired)
|
||||
is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood)
|
||||
is FingerEnrollState.EnrollHelp -> onEnrollmentHelp()
|
||||
is FingerEnrollState.PointerDown -> onPointerDown()
|
||||
is FingerEnrollState.PointerUp -> onPointerUp()
|
||||
is FingerEnrollState.OverlayShown -> overlayShown()
|
||||
is FingerEnrollState.EnrollError ->
|
||||
throw IllegalArgumentException("$TAG should not handle udfps error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,9 +136,37 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
fun setAccessibilityEnabled(enabled: Boolean) {
|
||||
this.isAccessibilityEnabled = enabled
|
||||
fingerprintProgressDrawable.setAccessibilityEnabled(enabled)
|
||||
if (enabled) {
|
||||
addHoverListener()
|
||||
} else {
|
||||
clearHoverListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun udfpsError(errMsgId: Int, errString: String) {}
|
||||
/**
|
||||
* Sends a touch exploration event to the [onHoverListener] this should only be used for
|
||||
* debugging.
|
||||
*/
|
||||
fun sendDebugTouchExplorationEvent(motionEvent: MotionEvent) {
|
||||
touchExplorationAnnouncer.onTouch(motionEvent)
|
||||
}
|
||||
|
||||
/** Sets the addHoverListener, this should happen when talkback is enabled. */
|
||||
private fun addHoverListener() {
|
||||
onHoverListener = OnHoverListener { _: View, event: MotionEvent ->
|
||||
sendDebugTouchExplorationEvent(event)
|
||||
false
|
||||
}
|
||||
this.setOnHoverListener(onHoverListener)
|
||||
}
|
||||
|
||||
/** Clears the hover listener if one was set. */
|
||||
private fun clearHoverListener() {
|
||||
val listener = OnHoverListener { _, _ -> false }
|
||||
this.setOnHoverListener(listener)
|
||||
onHoverListener = listener
|
||||
}
|
||||
|
||||
private fun overlayShown() {
|
||||
Log.e(TAG, "Implement overlayShown")
|
||||
@@ -115,7 +185,7 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
|
||||
/** Receive onAcquired event */
|
||||
private fun onAcquired(isAcquiredGood: Boolean) {
|
||||
val animateIfLastStepGood = isAcquiredGood && mRemainingSteps <= 2 && mRemainingSteps >= 0
|
||||
val animateIfLastStepGood = isAcquiredGood && remainingSteps <= 2 && remainingSteps >= 0
|
||||
if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired()
|
||||
}
|
||||
|
||||
@@ -129,6 +199,52 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
fingerprintIcon.startDrawing()
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
// Because the layout has changed, we need to recompute all locations.
|
||||
if (this::sensorRect.isInitialized && this::fingerprintSensorType.isInitialized) {
|
||||
setSensorRect(sensorRect, fingerprintSensorType)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is responsible for announcing touch events that are outside of the sensort rect
|
||||
* area. Generally, if a touch is to the left of the sensor, the accessibility announcement will
|
||||
* be something like "move right"
|
||||
*/
|
||||
private class TouchExplorationAnnouncer(
|
||||
val context: Context,
|
||||
val view: View,
|
||||
val overlayParams: UdfpsOverlayParams,
|
||||
val udfpsUtils: UdfpsUtils,
|
||||
) {
|
||||
/** Will announce accessibility event for touches outside of the sensor rect. */
|
||||
fun onTouch(event: MotionEvent) {
|
||||
val scaledTouch: Point =
|
||||
udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams)
|
||||
if (udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) {
|
||||
return
|
||||
}
|
||||
val theStr: String =
|
||||
udfpsUtils.onTouchOutsideOfSensorArea(
|
||||
true /*touchExplorationEnabled*/,
|
||||
context,
|
||||
scaledTouch.x,
|
||||
scaledTouch.y,
|
||||
overlayParams,
|
||||
)
|
||||
if (theStr != null) {
|
||||
view.announceForAccessibility(theStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Indicates we should should restore the views saved state. */
|
||||
fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) {
|
||||
fingerprintIcon.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
|
||||
fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UdfpsEnrollView"
|
||||
}
|
||||
|
@@ -18,9 +18,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
@@ -63,7 +65,7 @@ class FingerprintEnrollEnrollingViewModel(
|
||||
}
|
||||
|
||||
/** Collects the enrollment flow based on [enrollFlowShouldBeRunning] */
|
||||
val enrollFLow =
|
||||
val enrollFlow =
|
||||
enrollFlowShouldBeRunning.transformLatest {
|
||||
if (it) {
|
||||
fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) }
|
||||
|
@@ -25,7 +25,6 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Orientatio
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
|
||||
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -38,7 +37,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */
|
||||
/** Models the UI state for fingerprint enroll education */
|
||||
class FingerprintEnrollFindSensorViewModel(
|
||||
private val navigationViewModel: FingerprintNavigationViewModel,
|
||||
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||
@@ -70,7 +69,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
combineTransform(
|
||||
_showSfpsLottie,
|
||||
foldStateInteractor.isFolded,
|
||||
orientationInteractor.rotation,
|
||||
orientationInteractor.rotationFromDefault,
|
||||
) { _, isFolded, rotation ->
|
||||
emit(Pair(isFolded, rotation))
|
||||
}
|
||||
@@ -147,6 +146,7 @@ class FingerprintEnrollFindSensorViewModel(
|
||||
}
|
||||
}
|
||||
is FingerEnrollState.EnrollHelp -> {}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -45,12 +45,14 @@ import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.util.toFingerprintEnrollOptions
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.binder.FingerprintSettingsViewBinder
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsNavigationViewModel
|
||||
import com.android.settings.biometrics.fingerprint2.ui.settings.viewmodel.FingerprintSettingsViewModel
|
||||
@@ -222,6 +224,13 @@ class FingerprintSettingsV2Fragment :
|
||||
val fingerprintSensorProvider =
|
||||
FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
|
||||
val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
|
||||
val fingerprintEnrollStateRepository =
|
||||
FingerprintEnrollInteractorImpl(
|
||||
requireContext().applicationContext,
|
||||
intent.toFingerprintEnrollOptions(),
|
||||
fingerprintManager,
|
||||
Settings,
|
||||
)
|
||||
|
||||
val interactor =
|
||||
FingerprintManagerInteractorImpl(
|
||||
@@ -230,9 +239,7 @@ class FingerprintSettingsV2Fragment :
|
||||
fingerprintManager,
|
||||
fingerprintSensorProvider,
|
||||
GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
|
||||
pressToAuthInteractor,
|
||||
Settings,
|
||||
getIntent()
|
||||
fingerprintEnrollStateRepository,
|
||||
)
|
||||
|
||||
val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
|
||||
|
@@ -91,6 +91,7 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
|
||||
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
|
||||
}
|
||||
|
@@ -17,9 +17,7 @@ package com.android.settings.tests.screenshot.biometrics.fingerprint.fragment
|
||||
*/
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||
import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.education.RfpsEnrollFindSensorFragment
|
||||
import com.android.settings.tests.screenshot.biometrics.fingerprint.Injector.Companion.BiometricFragmentScreenShotRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -28,10 +26,7 @@ import platform.test.screenshot.FragmentScreenshotTestRule
|
||||
import platform.test.screenshot.ViewScreenshotTestRule.Mode
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FingerprintEnrollFindSensorScreenshotTest {
|
||||
private val injector: Injector =
|
||||
Injector(FingerprintNavigationStep.Education(Injector.interactor.sensorProp))
|
||||
|
||||
class RfpsEnrollFindSensorScreenshotTest {
|
||||
@Rule @JvmField var rule: FragmentScreenshotTestRule = BiometricFragmentScreenShotRule()
|
||||
|
||||
@Test
|
||||
@@ -39,7 +34,7 @@ class FingerprintEnrollFindSensorScreenshotTest {
|
||||
rule.screenshotTest(
|
||||
"fp_enroll_find_sensor",
|
||||
Mode.MatchSize,
|
||||
FingerprintEnrollFindSensorV2Fragment(injector.fingerprintSensor.sensorType, injector.factory),
|
||||
RfpsEnrollFindSensorFragment(),
|
||||
)
|
||||
}
|
||||
}
|
@@ -33,8 +33,8 @@ import android.os.Handler
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
|
||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.Default
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
||||
@@ -82,10 +82,6 @@ class FingerprintManagerInteractorTest {
|
||||
@Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
|
||||
|
||||
private var testScope = TestScope(backgroundDispatcher)
|
||||
private var pressToAuthInteractor =
|
||||
object : PressToAuthInteractor {
|
||||
override val isEnabled = flowOf(false)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
@@ -113,9 +109,12 @@ class FingerprintManagerInteractorTest {
|
||||
fingerprintManager,
|
||||
fingerprintSensorRepository,
|
||||
gateKeeperPasswordProvider,
|
||||
pressToAuthInteractor,
|
||||
Default,
|
||||
Intent(),
|
||||
FingerprintEnrollInteractorImpl(
|
||||
context,
|
||||
FingerprintEnrollOptions.Builder().build(),
|
||||
fingerprintManager,
|
||||
Default,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -145,7 +145,8 @@ class FingerprintEnrollFindSensorViewModelV2Test {
|
||||
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 =
|
||||
|
Reference in New Issue
Block a user