UDFPS Enrollment refactor (2/N)
This commit is focused on UI. The guided enrollment, lottie, text and progress bar should all be working according to the previous experience. Test: atest Bug: 297082837 Change-Id: I9b414053f5eaf7b2bc164dacdddc96ed44fec6cb
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
~ 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");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
~ you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
@@ -15,56 +15,77 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<com.google.android.setupdesign.GlifLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
|
|
||||||
android:id="@+id/setup_wizard_layout"
|
android:id="@+id/setup_wizard_layout"
|
||||||
style="?attr/fingerprint_layout_theme"
|
style="?attr/fingerprint_layout_theme"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
>
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<!-- This is used to grab style attributes and apply them
|
||||||
style="@style/SudContentFrame"
|
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"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="80dp"
|
||||||
|
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="200dp"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical">
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
app:lottie_autoPlay="true"
|
||||||
|
app:lottie_loop="true"
|
||||||
|
app:lottie_speed=".85" />
|
||||||
|
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/layout_container"
|
||||||
android:layout_height="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center|bottom"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:orientation="vertical">
|
android:clipToPadding="false"
|
||||||
|
>
|
||||||
|
|
||||||
<FrameLayout
|
<include layout="@layout/fingerprint_v2_udfps_enroll_view" />
|
||||||
android:id="@+id/layout_container"
|
</FrameLayout>
|
||||||
android:layout_width="200dp"
|
|
||||||
android:layout_height="200dp"
|
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
tools:ignore="Suspicious0dp">
|
|
||||||
|
|
||||||
<!-- Animation res MUST be set in code -->
|
|
||||||
<com.airbnb.lottie.LottieAnimationView
|
|
||||||
android:id="@+id/illustration_lottie"
|
|
||||||
android:layout_width="200dp"
|
|
||||||
android:layout_height="200dp"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:scaleType="centerInside"
|
|
||||||
app:lottie_autoPlay="true"
|
|
||||||
app:lottie_loop="true"
|
|
||||||
app:lottie_speed=".85" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</com.google.android.setupdesign.GlifLayout>
|
</LinearLayout>
|
||||||
|
38
res/layout/fingerprint_v2_udfps_enroll_view.xml
Normal file
38
res/layout/fingerprint_v2_udfps_enroll_view.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/udfps_animation_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/udfps_enroll_animation_fp_progress_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/udfps_enroll_animation_fp_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2>
|
@@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.transform
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,12 +46,12 @@ interface FingerprintSensorRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FingerprintSensorRepositoryImpl(
|
class FingerprintSensorRepositoryImpl(
|
||||||
fingerprintManager: FingerprintManager,
|
fingerprintManager: FingerprintManager,
|
||||||
backgroundDispatcher: CoroutineDispatcher,
|
backgroundDispatcher: CoroutineDispatcher,
|
||||||
activityScope: CoroutineScope,
|
activityScope: CoroutineScope,
|
||||||
) : FingerprintSensorRepository {
|
) : FingerprintSensorRepository {
|
||||||
|
|
||||||
override val fingerprintSensor: Flow<FingerprintSensor> =
|
private val fingerprintPropsInternal: Flow<FingerprintSensorPropertiesInternal> =
|
||||||
callbackFlow {
|
callbackFlow {
|
||||||
val callback =
|
val callback =
|
||||||
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
|
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
|
||||||
@@ -60,7 +61,7 @@ class FingerprintSensorRepositoryImpl(
|
|||||||
if (sensors.isEmpty()) {
|
if (sensors.isEmpty()) {
|
||||||
trySend(DEFAULT_PROPS)
|
trySend(DEFAULT_PROPS)
|
||||||
} else {
|
} else {
|
||||||
trySend(sensors[0].toFingerprintSensor())
|
trySend(sensors[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,20 +72,24 @@ class FingerprintSensorRepositoryImpl(
|
|||||||
}
|
}
|
||||||
.stateIn(activityScope, started = SharingStarted.Eagerly, initialValue = DEFAULT_PROPS)
|
.stateIn(activityScope, started = SharingStarted.Eagerly, initialValue = DEFAULT_PROPS)
|
||||||
|
|
||||||
companion object {
|
override val fingerprintSensor: Flow<FingerprintSensor> =
|
||||||
private const val TAG = "FingerprintSensorRepoImpl"
|
fingerprintPropsInternal.transform {
|
||||||
|
emit(it.toFingerprintSensor())
|
||||||
private val DEFAULT_PROPS =
|
|
||||||
FingerprintSensorPropertiesInternal(
|
|
||||||
-1 /* sensorId */,
|
|
||||||
SensorProperties.STRENGTH_CONVENIENCE,
|
|
||||||
0 /* maxEnrollmentsPerUser */,
|
|
||||||
listOf<ComponentInfoInternal>(),
|
|
||||||
FingerprintSensorProperties.TYPE_UNKNOWN,
|
|
||||||
false /* halControlsIllumination */,
|
|
||||||
true /* resetLockoutRequiresHardwareAuthToken */,
|
|
||||||
listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
|
|
||||||
)
|
|
||||||
.toFingerprintSensor()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "FingerprintSensorRepoImpl"
|
||||||
|
|
||||||
|
private val DEFAULT_PROPS =
|
||||||
|
FingerprintSensorPropertiesInternal(
|
||||||
|
-1 /* sensorId */,
|
||||||
|
SensorProperties.STRENGTH_CONVENIENCE,
|
||||||
|
0 /* maxEnrollmentsPerUser */,
|
||||||
|
listOf<ComponentInfoInternal>(),
|
||||||
|
FingerprintSensorProperties.TYPE_UNKNOWN,
|
||||||
|
false /* halControlsIllumination */,
|
||||||
|
true /* resetLockoutRequiresHardwareAuthToken */,
|
||||||
|
listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,10 +20,12 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.hardware.biometrics.BiometricConstants;
|
import android.hardware.biometrics.BiometricConstants;
|
||||||
import android.hardware.biometrics.BiometricFingerprintConstants
|
import android.hardware.biometrics.BiometricFingerprintConstants
|
||||||
|
import android.hardware.biometrics.SensorLocationInternal
|
||||||
import android.hardware.fingerprint.FingerprintEnrollOptions;
|
import android.hardware.fingerprint.FingerprintEnrollOptions;
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
|
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
|
||||||
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
import android.hardware.fingerprint.FingerprintManager.RemovalCallback
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
import com.android.settings.biometrics.GatekeeperPasswordProvider
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
|
package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
|
||||||
|
|
||||||
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||||
import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
|
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.FingerEnrollState
|
||||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
|
||||||
|
@@ -33,7 +33,6 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
|
|
||||||
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSIconTouchViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.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.viewmodel.RFPSViewModel
|
||||||
@@ -42,7 +41,6 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
|
|||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
|
|
||||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment
|
||||||
import com.google.android.setupcompat.template.FooterBarMixin
|
import com.google.android.setupcompat.template.FooterBarMixin
|
||||||
import com.google.android.setupcompat.template.FooterButton
|
import com.google.android.setupcompat.template.FooterButton
|
||||||
@@ -86,6 +84,7 @@ class RFPSEnrollFragment() : Fragment(R.layout.fingerprint_v2_rfps_enroll_enroll
|
|||||||
private val backgroundViewModel: BackgroundViewModel by lazy {
|
private val backgroundViewModel: BackgroundViewModel by lazy {
|
||||||
viewModelProvider[BackgroundViewModel::class.java]
|
viewModelProvider[BackgroundViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@@ -24,7 +24,6 @@ import android.graphics.drawable.AnimatedVectorDrawable
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
|
||||||
import android.view.animation.AnimationUtils
|
import android.view.animation.AnimationUtils
|
||||||
import android.view.animation.Interpolator
|
import android.view.animation.Interpolator
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
@@ -82,7 +81,6 @@ class RFPSProgressBar : RingProgressBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldAnimateInternal = shouldAnimate
|
shouldAnimateInternal = shouldAnimate
|
||||||
|
|
||||||
}
|
}
|
||||||
/** This function should only be called when actual progress has been made. */
|
/** This function should only be called when actual progress has been made. */
|
||||||
fun updateProgress(percentComplete: Float) {
|
fun updateProgress(percentComplete: Float) {
|
||||||
|
@@ -17,8 +17,9 @@
|
|||||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment
|
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -28,89 +29,23 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import com.airbnb.lottie.LottieAnimationView
|
import com.airbnb.lottie.LottieAnimationView
|
||||||
import com.airbnb.lottie.LottieCompositionFactory
|
import com.airbnb.lottie.LottieCompositionFactory
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
|
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.StageViewModel
|
||||||
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.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
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||||
import com.google.android.setupdesign.GlifLayout
|
import com.google.android.setupdesign.GlifLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
|
/** This fragment is responsible for showing the udfps Enrollment UI. */
|
||||||
class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enrolling) {
|
class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enrolling) {
|
||||||
|
|
||||||
/** Used for testing purposes */
|
/** Used for testing purposes */
|
||||||
private var factory: ViewModelProvider.Factory? = null
|
private var factory: ViewModelProvider.Factory? = null
|
||||||
private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] }
|
private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] }
|
||||||
|
private lateinit var udfpsEnrollView: UdfpsEnrollViewV2
|
||||||
@VisibleForTesting
|
|
||||||
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
|
||||||
factory = theFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
val layout = view as GlifLayout
|
|
||||||
val illustrationLottie: LottieAnimationView = layout.findViewById(R.id.illustration_lottie)!!
|
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
viewModel.stageFlow.collect {
|
|
||||||
layout.setHeaderText(getHeaderText(it))
|
|
||||||
getDescriptionText(it)?.let { descriptionText ->
|
|
||||||
layout.setDescriptionText(descriptionText)
|
|
||||||
}
|
|
||||||
getLottie(it)?.let { lottie ->
|
|
||||||
layout.descriptionText = ""
|
|
||||||
LottieCompositionFactory.fromRawRes(requireContext().applicationContext, lottie)
|
|
||||||
.addListener { comp ->
|
|
||||||
comp?.let { composition ->
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
illustrationLottie.setComposition(composition)
|
|
||||||
illustrationLottie.visibility = View.VISIBLE
|
|
||||||
illustrationLottie.playAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getHeaderText(stageViewModel: StageViewModel): Int {
|
|
||||||
return when (stageViewModel) {
|
|
||||||
StageViewModel.Center,
|
|
||||||
StageViewModel.Guided,
|
|
||||||
StageViewModel.Fingertip,
|
|
||||||
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
|
|
||||||
StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
|
|
||||||
StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDescriptionText(stageViewModel: StageViewModel): Int? {
|
|
||||||
return when (stageViewModel) {
|
|
||||||
StageViewModel.Center,
|
|
||||||
StageViewModel.Guided,
|
|
||||||
StageViewModel.Fingertip,
|
|
||||||
StageViewModel.LeftEdge,
|
|
||||||
StageViewModel.RightEdge -> null
|
|
||||||
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLottie(stageViewModel: StageViewModel): Int? {
|
|
||||||
return when (stageViewModel) {
|
|
||||||
StageViewModel.Center,
|
|
||||||
StageViewModel.Guided -> R.raw.udfps_center_hint_lottie
|
|
||||||
StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie
|
|
||||||
StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
|
|
||||||
StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
|
|
||||||
StageViewModel.Unknown -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val viewModelProvider: ViewModelProvider by lazy {
|
private val viewModelProvider: ViewModelProvider by lazy {
|
||||||
if (factory != null) {
|
if (factory != null) {
|
||||||
@@ -120,6 +55,111 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
constructor(theFactory: ViewModelProvider.Factory) : this() {
|
||||||
|
factory = theFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val illustrationLottie: LottieAnimationView = 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)!!
|
||||||
|
|
||||||
|
val glifLayout = view.findViewById<GlifLayout>(R.id.dummy_glif_layout)!!
|
||||||
|
val backgroundColor = glifLayout.backgroundBaseColor
|
||||||
|
val window = requireActivity().window
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||||
|
val color = backgroundColor?.defaultColor ?: glifLayout.primaryColor.defaultColor
|
||||||
|
window.statusBarColor = color
|
||||||
|
view.setBackgroundColor(color)
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.headerText.collect { titleTextView.setText(it.toResource()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.descriptionText.collect {
|
||||||
|
if (it != null) {
|
||||||
|
it.toResource()?.let { text -> descriptionTextView.setText(text) }
|
||||||
|
} else {
|
||||||
|
descriptionTextView.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.sensorLocation.collect { rect -> udfpsEnrollView.setSensorRect(rect) }
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.accessibilityEnabled.collect { isEnabled -> udfpsEnrollView.setAccessibilityEnabled(isEnabled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.lottie.collect { lottieModel ->
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
illustrationLottie.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.udfpsEvent.collect { udfpsEnrollView.onUdfpsEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.enrollStage.collect { udfpsEnrollView.updateStage(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HeaderText.toResource(): Int {
|
||||||
|
return when (this.stageViewModel) {
|
||||||
|
StageViewModel.Center,
|
||||||
|
StageViewModel.Guided,
|
||||||
|
StageViewModel.Fingertip,
|
||||||
|
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
|
||||||
|
StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
|
||||||
|
StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DescriptionText.toResource(): Int? {
|
||||||
|
return when (this.stageViewModel) {
|
||||||
|
StageViewModel.Center,
|
||||||
|
StageViewModel.Guided,
|
||||||
|
StageViewModel.Fingertip,
|
||||||
|
StageViewModel.LeftEdge,
|
||||||
|
StageViewModel.RightEdge -> null
|
||||||
|
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun EducationAnimationModel.toResource(): Int? {
|
||||||
|
return when (this.stageViewModel) {
|
||||||
|
StageViewModel.Center,
|
||||||
|
StageViewModel.Guided -> R.raw.udfps_center_hint_lottie
|
||||||
|
StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie
|
||||||
|
StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
|
||||||
|
StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
|
||||||
|
StageViewModel.Unknown -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "UDFPSEnrollFragment"
|
private const val TAG = "UDFPSEnrollFragment"
|
||||||
private val navStep = FingerprintNavigationStep.Enrollment::class
|
private val navStep = FingerprintNavigationStep.Enrollment::class
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/** Represents the description text for UDFPS enrollment */
|
||||||
|
data class DescriptionText(
|
||||||
|
val isSuw: Boolean,
|
||||||
|
val isAccessibility: Boolean,
|
||||||
|
val stageViewModel: StageViewModel,
|
||||||
|
)
|
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/** Represents the lottie for UDFPS enrollment */
|
||||||
|
data class EducationAnimationModel(
|
||||||
|
val isSuw: Boolean,
|
||||||
|
val isAccessibility: Boolean,
|
||||||
|
val stageViewModel: StageViewModel,
|
||||||
|
)
|
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/** Represents the header text for UDFPS enrollment */
|
||||||
|
data class HeaderText(
|
||||||
|
val isSuw: Boolean,
|
||||||
|
val isAccessibility: Boolean,
|
||||||
|
val stageViewModel: StageViewModel,
|
||||||
|
)
|
@@ -22,15 +22,30 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
|
|||||||
* of the where the user should press their fingerprint
|
* of the where the user should press their fingerprint
|
||||||
*/
|
*/
|
||||||
sealed class StageViewModel {
|
sealed class StageViewModel {
|
||||||
|
/** Unknown stage */
|
||||||
data object Unknown : StageViewModel()
|
data object Unknown : StageViewModel()
|
||||||
|
|
||||||
|
/** This is the stage that moves the fingerprint icon around during enrollment. */
|
||||||
data object Guided : StageViewModel()
|
data object Guided : StageViewModel()
|
||||||
|
|
||||||
|
/** The center stage is the initial stage of enrollment. */
|
||||||
data object Center : StageViewModel()
|
data object Center : StageViewModel()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint stage of enrollment. Typically there is some sort of indication that a user should
|
||||||
|
* be using their finger tip to enroll.
|
||||||
|
*/
|
||||||
data object Fingertip : StageViewModel()
|
data object Fingertip : StageViewModel()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left edge stage of enrollment. Typically there is an indication that a user should be using the
|
||||||
|
* left edge of their fingerprint.
|
||||||
|
*/
|
||||||
data object LeftEdge : StageViewModel()
|
data object LeftEdge : StageViewModel()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Right edge stage of enrollment. Typically there is an indication that a user should be using
|
||||||
|
* the right edge of their fingerprint.
|
||||||
|
*/
|
||||||
data object RightEdge : StageViewModel()
|
data object RightEdge : StageViewModel()
|
||||||
}
|
}
|
||||||
|
@@ -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.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,16 +16,93 @@
|
|||||||
|
|
||||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||||
|
|
||||||
|
import android.graphics.Rect
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
|
/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
|
||||||
class UdfpsViewModel() : ViewModel() {
|
class UdfpsViewModel() : ViewModel() {
|
||||||
|
|
||||||
/** Indicates what stage UDFPS enrollment is in. */
|
private val isSetupWizard = flowOf(false)
|
||||||
val stageFlow = flowOf(StageViewModel.Center)
|
|
||||||
|
/** 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)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filterNotNull()
|
||||||
|
|
||||||
|
/** The header text for UDFPS enrollment */
|
||||||
|
val headerText: Flow<HeaderText> =
|
||||||
|
combine(isSetupWizard, accessibilityEnabled, enrollStage) { isSuw, isAccessibility, stage ->
|
||||||
|
return@combine HeaderText(isSuw, isAccessibility, stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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()
|
||||||
|
|
||||||
class UdfpsEnrollmentFactory() : ViewModelProvider.Factory {
|
class UdfpsEnrollmentFactory() : ViewModelProvider.Factory {
|
||||||
|
|
||||||
@@ -38,5 +115,58 @@ class UdfpsViewModel() : ViewModel() {
|
|||||||
companion object {
|
companion object {
|
||||||
private val navStep = FingerprintNavigationStep.Enrollment::class
|
private val navStep = FingerprintNavigationStep.Enrollment::class
|
||||||
private const val TAG = "UDFPSViewModel"
|
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),
|
||||||
|
CreateProgress(10, 10),
|
||||||
|
CreateProgress(9, 10),
|
||||||
|
CreateProgress(8, 10),
|
||||||
|
CreateProgress(7, 10),
|
||||||
|
CreateProgress(6, 10),
|
||||||
|
CreateProgress(5, 10),
|
||||||
|
CreateProgress(4, 10),
|
||||||
|
CreateProgress(3, 10),
|
||||||
|
CreateProgress(2, 10),
|
||||||
|
CreateProgress(1, 10),
|
||||||
|
CreateProgress(0, 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** This will be removed */
|
||||||
|
private fun CreateProgress(remaining: Int, total: Int): List<UdfpsEnrollEvent> {
|
||||||
|
return listOf(PointerDown, Acquired(true), UdfpsProgress(remaining, total), PointerUp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/** Keeps track of which guided enrollment point we should be using */
|
||||||
|
class UdfpsEnrollHelperV2(private val mContext: Context) {
|
||||||
|
|
||||||
|
private var isGuidedEnrollment: Boolean = false
|
||||||
|
private val accessibilityEnabled: Boolean
|
||||||
|
private val guidedEnrollmentPoints: MutableList<PointF>
|
||||||
|
private var index = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
val am = mContext.getSystemService(AccessibilityManager::class.java)
|
||||||
|
accessibilityEnabled = am!!.isEnabled
|
||||||
|
guidedEnrollmentPoints = ArrayList()
|
||||||
|
|
||||||
|
// Number of pixels per mm
|
||||||
|
val px =
|
||||||
|
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, mContext.resources.displayMetrics)
|
||||||
|
guidedEnrollmentPoints.add(PointF(2.00f * px, 0.00f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(0.87f * px, -2.70f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-1.80f * px, -1.31f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-1.80f * px, 1.31f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(0.88f * px, 2.70f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(3.94f * px, -1.06f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(2.90f * px, -4.14f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-0.52f * px, -5.95f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-3.33f * px, -3.33f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-3.99f * px, -0.35f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-3.62f * px, 2.54f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(-1.49f * px, 5.57f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(2.29f * px, 4.92f * px))
|
||||||
|
guidedEnrollmentPoints.add(PointF(3.82f * px, 1.78f * px))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This indicates whether we should be offsetting the enrollment icon based on
|
||||||
|
* [guidedEnrollmentPoints]
|
||||||
|
*/
|
||||||
|
fun onUpdateStage(stage: StageViewModel) {
|
||||||
|
this.isGuidedEnrollment = stage is StageViewModel.Guided
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates [index] to be used by [guidedEnrollmentPoints] */
|
||||||
|
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||||
|
index = totalSteps - remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current guided enrollment point, or (0,0) if we are not in guided enrollment or are
|
||||||
|
* in accessibility.
|
||||||
|
*/
|
||||||
|
val guidedEnrollmentLocation: PointF?
|
||||||
|
get() {
|
||||||
|
if (accessibilityEnabled || !isGuidedEnrollment) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var scale = SCALE
|
||||||
|
val originalPoint = guidedEnrollmentPoints[index % guidedEnrollmentPoints.size]
|
||||||
|
return PointF(originalPoint.x * scale, originalPoint.y * scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "UdfpsEnrollHelperV2"
|
||||||
|
private const val SCALE = 0.5f
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget
|
||||||
|
|
||||||
|
import android.animation.AnimatorSet
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.annotation.ColorInt
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PixelFormat
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.ShapeDrawable
|
||||||
|
import android.graphics.drawable.shapes.PathShape
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.PathParser
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
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 kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for drawing the udfps icon, and to update its movement based on the
|
||||||
|
* various stages of enrollment
|
||||||
|
*/
|
||||||
|
class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() {
|
||||||
|
private var targetAnimatorSet: AnimatorSet? = null
|
||||||
|
private val movingTargetFpIcon: Drawable
|
||||||
|
private val fingerprintDrawable: ShapeDrawable
|
||||||
|
private val sensorOutlinePaint: Paint
|
||||||
|
private val blueFill: Paint
|
||||||
|
private val helper = UdfpsEnrollHelperV2(context)
|
||||||
|
@ColorInt private var enrollIconColor = 0
|
||||||
|
@ColorInt private var movingTargetFill = 0
|
||||||
|
private var currentScale = 1.0f
|
||||||
|
private var alpha = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt]
|
||||||
|
*/
|
||||||
|
private val sensorRectBounds: Rect = Rect()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following values are used to describe where the icon should be drawn. [currX] and [currY]
|
||||||
|
* are changed based on the current guided enrollment step which is given by the
|
||||||
|
* [UdfpsEnrollHelperV2]
|
||||||
|
*/
|
||||||
|
private var currX = 0f
|
||||||
|
private var currY = 0f
|
||||||
|
|
||||||
|
private var sensorWidth = 0f
|
||||||
|
private var sensorHeight = 0f
|
||||||
|
|
||||||
|
init {
|
||||||
|
fingerprintDrawable = createUdfpsIcon(context)
|
||||||
|
context
|
||||||
|
.obtainStyledAttributes(
|
||||||
|
attrs,
|
||||||
|
R.styleable.BiometricsEnrollView,
|
||||||
|
R.attr.biometricsEnrollStyle,
|
||||||
|
R.style.BiometricsEnrollStyle,
|
||||||
|
)
|
||||||
|
.let {
|
||||||
|
enrollIconColor = it.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0)
|
||||||
|
movingTargetFill =
|
||||||
|
it.getColor(R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0)
|
||||||
|
it.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
movingTargetFpIcon = context.resources.getDrawable(R.drawable.ic_enrollment_fingerprint, null).apply {
|
||||||
|
setTint(enrollIconColor)
|
||||||
|
mutate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerprintDrawable.setTint(enrollIconColor)
|
||||||
|
setAlpha(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAlpha(): Int {
|
||||||
|
return alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {}
|
||||||
|
|
||||||
|
override fun getOpacity(): Int {
|
||||||
|
return PixelFormat.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
this.alpha = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [sensorRect] coordinates for the sensor area. The [sensorRect] should be the coordinates
|
||||||
|
* with respect to its root frameview
|
||||||
|
*/
|
||||||
|
fun drawSensorRectAt(sensorRect: Rect) {
|
||||||
|
Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect($sensorRect)")
|
||||||
|
sensorRectBounds.set(sensorRect)
|
||||||
|
fingerprintDrawable.bounds = sensorRect
|
||||||
|
movingTargetFpIcon.bounds = sensorRect
|
||||||
|
currX = sensorRect.left.toFloat()
|
||||||
|
currY = sensorRect.top.toFloat()
|
||||||
|
sensorWidth = (sensorRect.right - sensorRect.left).toFloat()
|
||||||
|
sensorHeight = (sensorRect.bottom - sensorRect.top).toFloat()
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the progress of the icon */
|
||||||
|
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||||
|
helper.onEnrollmentProgress(remaining, totalSteps)
|
||||||
|
val offset = helper.guidedEnrollmentLocation
|
||||||
|
val currentBounds = getCurrLocation().toRect()
|
||||||
|
if (offset != null) {
|
||||||
|
// This is the desired location of the sensor rect, the [EnrollHelper]
|
||||||
|
// 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 =
|
||||||
|
!currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f
|
||||||
|
if (shouldAnimateMovement) {
|
||||||
|
targetAnimatorSet?.let { it.cancel() }
|
||||||
|
animateMovement(currentBounds, targetRect, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we are not offsetting the sensor, move it back to its original place
|
||||||
|
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the stage of the icon */
|
||||||
|
fun updateStage(it: StageViewModel) {
|
||||||
|
helper.onUpdateStage(it)
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
val currLocation = getCurrLocation()
|
||||||
|
canvas.scale(currentScale, currentScale, currLocation.centerX(), currLocation.centerY())
|
||||||
|
|
||||||
|
sensorRectBounds?.let { canvas.drawOval(currLocation, sensorOutlinePaint) }
|
||||||
|
fingerprintDrawable.bounds = currLocation.toRect()
|
||||||
|
fingerprintDrawable.draw(canvas)
|
||||||
|
fingerprintDrawable.setAlpha(alpha)
|
||||||
|
sensorOutlinePaint.setAlpha(alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrLocation(): RectF =
|
||||||
|
RectF(currX, currY, currX + sensorWidth, currY + sensorHeight)
|
||||||
|
|
||||||
|
private fun animateMovement(currentBounds: Rect, offsetRect: RectF, scaleMovement: Boolean) {
|
||||||
|
if (currentBounds.equals(offsetRect)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left)
|
||||||
|
xAnimator.addUpdateListener {
|
||||||
|
currX = it.animatedValue as Float
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
val yAnimator = ValueAnimator.ofFloat(currentBounds.top.toFloat(), offsetRect.top)
|
||||||
|
yAnimator.addUpdateListener {
|
||||||
|
currY = it.animatedValue as Float
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
val animators = mutableListOf(xAnimator, yAnimator)
|
||||||
|
val duration = TARGET_ANIM_DURATION_LONG
|
||||||
|
if (scaleMovement) {
|
||||||
|
val scaleAnimator = ValueAnimator.ofFloat(0f, Math.PI.toFloat())
|
||||||
|
scaleAnimator.setDuration(duration)
|
||||||
|
scaleAnimator.addUpdateListener { animation: ValueAnimator ->
|
||||||
|
// Grow then shrink
|
||||||
|
currentScale =
|
||||||
|
(1 + SCALE_MAX * sin((animation.getAnimatedValue() as Float).toDouble()).toFloat())
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
scaleAnimator.addListener(onEnd = { currentScale = 1f })
|
||||||
|
animators.add(scaleAnimator)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAnimatorSet = AnimatorSet()
|
||||||
|
|
||||||
|
targetAnimatorSet?.let {
|
||||||
|
it.interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
it.setDuration(duration)
|
||||||
|
it.playTogether(animators.toList())
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "UdfpsEnrollDrawableV2"
|
||||||
|
private const val DEFAULT_STROKE_WIDTH = 3f
|
||||||
|
private const val TARGET_ANIM_DURATION_LONG = 800L
|
||||||
|
private const val SCALE_MAX = 0.25f
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return drawable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,362 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.animation.ValueAnimator.AnimatorUpdateListener
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorFilter
|
||||||
|
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 androidx.annotation.ColorInt
|
||||||
|
import androidx.core.graphics.toRectF
|
||||||
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
|
import com.android.settings.R
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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?) :
|
||||||
|
Drawable() {
|
||||||
|
private val sensorRect: Rect = Rect()
|
||||||
|
private val strokeWidthPx: Float
|
||||||
|
|
||||||
|
@ColorInt private val progressColor: Int
|
||||||
|
@ColorInt private var helpColor: Int = 0
|
||||||
|
@ColorInt private val onFirstBucketFailedColor: Int
|
||||||
|
|
||||||
|
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
|
||||||
|
private var totalSteps = 0
|
||||||
|
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
|
||||||
|
|
||||||
|
init {
|
||||||
|
val ta =
|
||||||
|
mContext.obtainStyledAttributes(
|
||||||
|
attrs,
|
||||||
|
R.styleable.BiometricsEnrollView,
|
||||||
|
R.attr.biometricsEnrollStyle,
|
||||||
|
R.style.BiometricsEnrollStyle,
|
||||||
|
)
|
||||||
|
movingTargetFill = ta.getColor(R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0)
|
||||||
|
movingTargetFillError =
|
||||||
|
ta.getColor(R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0)
|
||||||
|
enrollProgressColor = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0)
|
||||||
|
enrollProgressHelp =
|
||||||
|
ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0)
|
||||||
|
enrollProgressHelpWithTalkback =
|
||||||
|
ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0)
|
||||||
|
ta.recycle()
|
||||||
|
val density = mContext.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)!!
|
||||||
|
|
||||||
|
progressBarRadius = mContext.resources.getInteger(R.integer.config_udfpsEnrollProgressBar)
|
||||||
|
|
||||||
|
progressUpdateListener = AnimatorUpdateListener { animation: ValueAnimator ->
|
||||||
|
progress = animation.getAnimatedValue() as Float
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
fillColorUpdateListener = AnimatorUpdateListener { animation: ValueAnimator ->
|
||||||
|
fillPaint.setColor(animation.getAnimatedValue() as Int)
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
backgroundColorUpdateListener = AnimatorUpdateListener { animation: ValueAnimator ->
|
||||||
|
backgroundPaint.setColor(animation.getAnimatedValue() as Int)
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicates enrollment progress has occurred. */
|
||||||
|
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||||
|
afterFirstTouch = true
|
||||||
|
updateState(remaining, totalSteps, false /* showingHelp */)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicates enrollment help has occurred. */
|
||||||
|
fun onEnrollmentHelp(remaining: Int, totalSteps: Int) {
|
||||||
|
updateState(remaining, totalSteps, true /* showingHelp */)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicates the last step was acquired. */
|
||||||
|
fun onLastStepAcquired() {
|
||||||
|
updateState(0, totalSteps, false /* showingHelp */)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw(canvas: Canvas) {
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rotate -90 degrees to make the progress start from the top right and not the bottom
|
||||||
|
// right
|
||||||
|
canvas.rotate(
|
||||||
|
-90f,
|
||||||
|
sensorProgressRect.centerX().toFloat(),
|
||||||
|
sensorProgressRect.centerY().toFloat(),
|
||||||
|
)
|
||||||
|
if (progress < 1f) {
|
||||||
|
// Draw the background color of the progress circle.
|
||||||
|
canvas.drawArc(
|
||||||
|
sensorProgressRect.toRectF(),
|
||||||
|
0f /* startAngle */,
|
||||||
|
360f /* sweepAngle */,
|
||||||
|
false /* useCenter */,
|
||||||
|
backgroundPaint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (progress > 0f) {
|
||||||
|
// Draw the filled portion of the progress circle.
|
||||||
|
canvas.drawArc(
|
||||||
|
sensorProgressRect.toRectF(),
|
||||||
|
0f /* startAngle */,
|
||||||
|
360f * progress /* sweepAngle */,
|
||||||
|
false /* useCenter */,
|
||||||
|
fillPaint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Do nothing here, we will control the alpha internally. */
|
||||||
|
override fun setAlpha(alpha: Int) {}
|
||||||
|
|
||||||
|
/** Do not apply color filters here for enrollment. */
|
||||||
|
override fun setColorFilter(colorFilter: ColorFilter?) {}
|
||||||
|
|
||||||
|
/** Legacy code, returning PixelFormat.UNKNOWN */
|
||||||
|
override fun getOpacity(): Int {
|
||||||
|
return PixelFormat.UNKNOWN
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Draws the progress with locations [sensorLocationX] [sensorLocationY], note these must be with
|
||||||
|
* respect to the parent framelayout.
|
||||||
|
*/
|
||||||
|
fun drawProgressAt(sensorRect: Rect) {
|
||||||
|
this.sensorRect.set(sensorRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicates if accessibility is enabled or not. */
|
||||||
|
fun setAccessibilityEnabled(enabled: Boolean) {
|
||||||
|
isAccessibilityEnabled = enabled
|
||||||
|
updateHelpColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateHelpColor() {
|
||||||
|
helpColor =
|
||||||
|
if (!isAccessibilityEnabled) {
|
||||||
|
enrollProgressHelp
|
||||||
|
} else {
|
||||||
|
enrollProgressHelpWithTalkback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateState(remainingSteps: Int, totalSteps: Int, showingHelp: Boolean) {
|
||||||
|
updateProgress(remainingSteps, totalSteps, showingHelp)
|
||||||
|
updateFillColor(showingHelp)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProgress(remainingSteps: Int, totalSteps: Int, showingHelp: Boolean) {
|
||||||
|
if (this.remainingSteps == remainingSteps && this.totalSteps == totalSteps) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 progressSteps = max(0.0, (totalSteps - remainingSteps).toDouble()).toInt()
|
||||||
|
|
||||||
|
// If needed, add 1 to progress and total steps to account for initial touch.
|
||||||
|
val adjustedSteps = if (afterFirstTouch) progressSteps + 1 else progressSteps
|
||||||
|
val adjustedTotal = if (afterFirstTouch) this.totalSteps + 1 else this.totalSteps
|
||||||
|
val targetProgress =
|
||||||
|
min(1.0, (adjustedSteps.toFloat() / adjustedTotal.toFloat()).toDouble()).toFloat()
|
||||||
|
if (progressAnimator != null && progressAnimator!!.isRunning) {
|
||||||
|
progressAnimator!!.cancel()
|
||||||
|
}
|
||||||
|
progressAnimator =
|
||||||
|
ValueAnimator.ofFloat(progress, targetProgress).also {
|
||||||
|
it.setDuration(PROGRESS_ANIMATION_DURATION_MS)
|
||||||
|
it.addUpdateListener(progressUpdateListener)
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
|
if (remainingSteps == 0) {
|
||||||
|
startCompletionAnimation()
|
||||||
|
} else if (remainingSteps > 0) {
|
||||||
|
rollBackCompletionAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun animateBackgroundColor() {
|
||||||
|
if (backgroundColorAnimator != null && backgroundColorAnimator!!.isRunning) {
|
||||||
|
backgroundColorAnimator!!.end()
|
||||||
|
}
|
||||||
|
backgroundColorAnimator =
|
||||||
|
ValueAnimator.ofArgb(backgroundPaint.color, onFirstBucketFailedColor).also {
|
||||||
|
it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS)
|
||||||
|
it.repeatCount = 1
|
||||||
|
it.repeatMode = ValueAnimator.REVERSE
|
||||||
|
it.interpolator = DEACCEL
|
||||||
|
it.addUpdateListener(backgroundColorUpdateListener)
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFillColor(showingHelp: Boolean) {
|
||||||
|
if (!afterFirstTouch && showingHelp) {
|
||||||
|
// If we are on the first touch, animate the background color
|
||||||
|
// instead of the progress color.
|
||||||
|
animateBackgroundColor()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (fillColorAnimator != null && fillColorAnimator!!.isRunning) {
|
||||||
|
fillColorAnimator!!.end()
|
||||||
|
}
|
||||||
|
@ColorInt val targetColor = if (showingHelp) helpColor else progressColor
|
||||||
|
fillColorAnimator =
|
||||||
|
ValueAnimator.ofArgb(fillPaint.color, targetColor).also {
|
||||||
|
it.setDuration(FILL_COLOR_ANIMATION_DURATION_MS)
|
||||||
|
it.repeatCount = 1
|
||||||
|
it.repeatMode = ValueAnimator.REVERSE
|
||||||
|
it.interpolator = DEACCEL
|
||||||
|
it.addUpdateListener(fillColorUpdateListener)
|
||||||
|
it.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCompletionAnimation() {
|
||||||
|
if (complete) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
complete = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rollBackCompletionAnimation() {
|
||||||
|
if (!complete) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
complete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* 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.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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].
|
||||||
|
*/
|
||||||
|
fun setSensorRect(rect: Rect) {
|
||||||
|
this.sensorRect = rect
|
||||||
|
post {
|
||||||
|
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 parentView = parent as ViewGroup
|
||||||
|
val coords = parentView.getLocationOnScreen()
|
||||||
|
val parentLeft = coords[0]
|
||||||
|
val parentTop = coords[1]
|
||||||
|
val sensorRectOffset = Rect(sensorRect)
|
||||||
|
sensorRectOffset.offset(-parentLeft, -parentTop)
|
||||||
|
|
||||||
|
fingerprintIcon.drawSensorRectAt(sensorRectOffset)
|
||||||
|
fingerprintProgressDrawable.drawProgressAt(sensorRectOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the current enrollment stage. */
|
||||||
|
fun updateStage(it: StageViewModel) {
|
||||||
|
fingerprintIcon.updateStage(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Receive enroll progress event */
|
||||||
|
fun onUdfpsEvent(event: UdfpsEnrollEvent) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicates if accessibility is enabled. */
|
||||||
|
fun setAccessibilityEnabled(enabled: Boolean) {
|
||||||
|
this.isAccessibilityEnabled = enabled
|
||||||
|
fingerprintProgressDrawable.setAccessibilityEnabled(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
invalidate()
|
||||||
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun udfpsError(errMsgId: Int, errString: String) {
|
||||||
|
Log.e(TAG, "Implement udfpsError")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun overlayShown() {
|
||||||
|
Log.e(TAG, "Implement overlayShown")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Receive enroll progress event */
|
||||||
|
private fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||||
|
post {
|
||||||
|
fingerprintIcon.onEnrollmentProgress(remaining, totalSteps)
|
||||||
|
fingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Receive enroll help event */
|
||||||
|
private fun onEnrollmentHelp() {
|
||||||
|
post { fingerprintProgressDrawable.onEnrollmentHelp(mRemainingSteps, mTotalSteps) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Receive onAcquired event */
|
||||||
|
private fun onAcquired(isAcquiredGood: Boolean) {
|
||||||
|
val animateIfLastStepGood = isAcquiredGood && mRemainingSteps <= 2 && mRemainingSteps >= 0
|
||||||
|
post { if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Receive onPointerDown event */
|
||||||
|
private fun onPointerDown() {}
|
||||||
|
|
||||||
|
/** Receive onPointerUp event */
|
||||||
|
private fun onPointerUp() {}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "UdfpsEnrollView"
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user