Merge "UDFPS Enrollment Refactor (5/N)" into main

This commit is contained in:
Joshua Mccloskey
2024-05-21 16:59:33 +00:00
committed by Android (Google) Code Review
18 changed files with 391 additions and 212 deletions

View File

@@ -14,38 +14,38 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.settings.biometrics.fingerprint2.lib.model package com.android.settings.biometrics.fingerprint2.data.model
/** /**
* A view model that describes the various stages of UDFPS Enrollment. This stages typically update * A view model that describes the various stages of UDFPS Enrollment. This stages typically update
* the enrollment UI in a major way, such as changing the lottie animation or changing the location * the enrollment UI in a major way, such as changing the lottie animation or changing the location
* of the where the user should press their fingerprint * of the where the user should press their fingerprint
*/ */
sealed class StageViewModel { sealed class EnrollStageModel {
/** Unknown stage */ /** Unknown stage */
data object Unknown : StageViewModel() data object Unknown : EnrollStageModel()
/** This is the stage that moves the fingerprint icon around during enrollment. */ /** This is the stage that moves the fingerprint icon around during enrollment. */
data object Guided : StageViewModel() data object Guided : EnrollStageModel()
/** The center stage is the initial stage of enrollment. */ /** The center stage is the initial stage of enrollment. */
data object Center : StageViewModel() data object Center : EnrollStageModel()
/** /**
* Fingerprint stage of enrollment. Typically there is some sort of indication that a user should * Fingerprint stage of enrollment. Typically there is some sort of indication that a user should
* be using their finger tip to enroll. * be using their finger tip to enroll.
*/ */
data object Fingertip : StageViewModel() data object Fingertip : EnrollStageModel()
/** /**
* Left edge stage of enrollment. Typically there is an indication that a user should be using the * Left edge stage of enrollment. Typically there is an indication that a user should be using the
* left edge of their fingerprint. * left edge of their fingerprint.
*/ */
data object LeftEdge : StageViewModel() data object LeftEdge : EnrollStageModel()
/** /**
* Right edge stage of enrollment. Typically there is an indication that a user should be using * Right edge stage of enrollment. Typically there is an indication that a user should be using
* the right edge of their fingerprint. * the right edge of their fingerprint.
*/ */
data object RightEdge : StageViewModel() data object RightEdge : EnrollStageModel()
} }

View File

@@ -16,11 +16,11 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor package com.android.settings.biometrics.fingerprint2.domain.interactor
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
typealias EnrollStageThresholds = Map<Float, StageViewModel> typealias EnrollStageThresholds = Map<Float, EnrollStageModel>
/** Interactor that provides enroll stages for enrollment. */ /** Interactor that provides enroll stages for enrollment. */
interface EnrollStageInteractor { interface EnrollStageInteractor {
@@ -33,11 +33,11 @@ class EnrollStageInteractorImpl() : EnrollStageInteractor {
override val enrollStageThresholds: Flow<EnrollStageThresholds> = override val enrollStageThresholds: Flow<EnrollStageThresholds> =
flowOf( flowOf(
mapOf( mapOf(
0.0f to StageViewModel.Center, 0.0f to EnrollStageModel.Center,
0.25f to StageViewModel.Guided, 0.25f to EnrollStageModel.Guided,
0.5f to StageViewModel.Fingertip, 0.5f to EnrollStageModel.Fingertip,
0.75f to StageViewModel.LeftEdge, 0.75f to EnrollStageModel.LeftEdge,
0.875f to StageViewModel.RightEdge, 0.875f to EnrollStageModel.RightEdge,
) )
) )
} }

View File

@@ -42,6 +42,7 @@ interface OrientationInteractor {
* A flow that contains the rotation info matched against the def [config_reverseDefaultRotation] * A flow that contains the rotation info matched against the def [config_reverseDefaultRotation]
*/ */
val rotationFromDefault: Flow<Int> val rotationFromDefault: Flow<Int>
/** /**
* A Helper function that computes rotation if device is in * A Helper function that computes rotation if device is in
* [R.bool.config_reverseDefaultConfigRotation] * [R.bool.config_reverseDefaultConfigRotation]

View File

@@ -0,0 +1,96 @@
/*
* 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.graphics.PointF
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
/**
* This interactor provides information about the current offset of the sensor for guided enrollment
* on UDFPS devices.
*/
interface UdfpsEnrollInteractor {
/** Indicates at which step a UDFPS enrollment is in. */
fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int)
/** Indicates if guided enrollment should be enabled or not. */
fun updateGuidedEnrollment(enabled: Boolean)
/**
* A flow indicating how much the sensor image drawable should be offset for guided enrollment. A
* null point indicates that the icon should be in its default position.
*/
val guidedEnrollmentOffset: Flow<PointF>
}
/** Keeps track of which guided enrollment point we should be using */
class UdfpsEnrollInteractorImpl(
pixelsPerMillimeter: Float,
accessibilityInteractor: AccessibilityInteractor,
) : UdfpsEnrollInteractor {
private var isGuidedEnrollment = MutableStateFlow(false)
// Number of pixels per mm
val px = pixelsPerMillimeter
private val guidedEnrollmentPoints: MutableList<PointF> =
mutableListOf(
PointF(2.00f * px, 0.00f * px),
PointF(0.87f * px, -2.70f * px),
PointF(-1.80f * px, -1.31f * px),
PointF(-1.80f * px, 1.31f * px),
PointF(0.88f * px, 2.70f * px),
PointF(3.94f * px, -1.06f * px),
PointF(2.90f * px, -4.14f * px),
PointF(-0.52f * px, -5.95f * px),
PointF(-3.33f * px, -3.33f * px),
PointF(-3.99f * px, -0.35f * px),
PointF(-3.62f * px, 2.54f * px),
PointF(-1.49f * px, 5.57f * px),
PointF(2.29f * px, 4.92f * px),
PointF(3.82f * px, 1.78f * px),
)
override fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int) {
val index = (totalStep - stepsRemaining) % guidedEnrollmentPoints.size
_guidedEnrollment.update { guidedEnrollmentPoints[index] }
}
override fun updateGuidedEnrollment(enabled: Boolean) {
isGuidedEnrollment.update { enabled }
}
private val _guidedEnrollment = MutableStateFlow(PointF(0f, 0f))
override val guidedEnrollmentOffset: Flow<PointF> =
combine(
_guidedEnrollment,
accessibilityInteractor.isAccessibilityEnabled,
isGuidedEnrollment,
) { point, accessibilityEnabled, guidedEnrollmentEnabled ->
if (accessibilityEnabled || !guidedEnrollmentEnabled) {
return@combine PointF(0f, 0f)
} else {
return@combine PointF(point.x * SCALE, point.y * SCALE)
}
}
companion object {
private const val SCALE = 0.5f
}
}

View File

@@ -24,6 +24,7 @@ import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle import android.os.Bundle
import android.os.Vibrator import android.os.Vibrator
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@@ -54,6 +55,8 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateI
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl 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.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl 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.Default
@@ -89,6 +92,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Fing
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Transition
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.android.settings.password.ChooseLockGeneric import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper import com.android.settings.password.ChooseLockSettingsHelper
@@ -116,6 +120,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var foldStateInteractor: FoldStateInteractor private lateinit var foldStateInteractor: FoldStateInteractor
private lateinit var orientationInteractor: OrientationInteractor private lateinit var orientationInteractor: OrientationInteractor
private lateinit var displayDensityInteractor: DisplayDensityInteractor private lateinit var displayDensityInteractor: DisplayDensityInteractor
private lateinit var udfpsEnrollInteractor: UdfpsEnrollInteractor
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var backgroundViewModel: BackgroundViewModel private lateinit var backgroundViewModel: BackgroundViewModel
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
@@ -256,6 +261,15 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
fingerprintManager, fingerprintManager,
Settings, Settings,
) )
val accessibilityInteractor =
AccessibilityInteractorImpl(
getSystemService(AccessibilityManager::class.java)!!,
lifecycleScope,
)
val pixelsPerMillimeter =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, context.resources.displayMetrics)
udfpsEnrollInteractor = UdfpsEnrollInteractorImpl(pixelsPerMillimeter, accessibilityInteractor)
val fingerprintManagerInteractor = val fingerprintManagerInteractor =
FingerprintManagerInteractorImpl( FingerprintManagerInteractorImpl(
@@ -273,12 +287,6 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo
val accessibilityInteractor =
AccessibilityInteractorImpl(
getSystemService(AccessibilityManager::class.java)!!,
lifecycleScope,
)
navigationViewModel = navigationViewModel =
ViewModelProvider( ViewModelProvider(
this, this,
@@ -384,6 +392,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
orientationInteractor, orientationInteractor,
backgroundViewModel, backgroundViewModel,
fingerprintSensorRepo, fingerprintSensorRepo,
udfpsEnrollInteractor,
), ),
)[UdfpsViewModel::class.java] )[UdfpsViewModel::class.java]
@@ -435,17 +444,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
else -> FingerprintEnrollEnrollingV2Fragment() else -> FingerprintEnrollEnrollingV2Fragment()
} }
} }
Introduction -> FingerprintEnrollIntroV2Fragment() is Introduction -> FingerprintEnrollIntroV2Fragment()
else -> null else -> null
} }
if (theClass != null) { if (theClass != null) {
supportFragmentManager.fragments.onEach { fragment ->
supportFragmentManager.beginTransaction().remove(fragment).commit()
}
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.setCustomAnimations(
step.enterTransition.toAnimation(),
step.exitTransition.toAnimation(),
)
.setReorderingAllowed(true) .setReorderingAllowed(true)
.add(R.id.fragment_container_view, theClass::class.java, null) .add(R.id.fragment_container_view, theClass::class.java, null)
.commit() .commit()
@@ -512,3 +521,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
} }
} }
} }
private fun Transition.toAnimation(): Int {
return when (this) {
Transition.EnterFromLeft -> com.google.android.setupdesign.R.anim.sud_slide_back_in
Transition.EnterFromRight -> com.google.android.setupdesign.R.anim.sud_slide_next_in
Transition.ExitToLeft -> com.google.android.setupdesign.R.anim.sud_slide_next_out
Transition.ExitToRight -> com.google.android.setupdesign.R.anim.sud_slide_back_out
}
}

View File

@@ -32,12 +32,12 @@ 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.data.model.EnrollStageModel
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.StageViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog 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.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.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.EducationAnimationModel
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.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2 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
@@ -83,6 +83,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
window.statusBarColor = color window.statusBarColor = color
view.setBackgroundColor(color) view.setBackgroundColor(color)
udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { repeatOnLifecycle(Lifecycle.State.RESUMED) {
launch { launch {
@@ -159,7 +161,14 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
} }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewModel.enrollStage.collect { udfpsEnrollView.updateStage(it) } viewModel.guidedEnrollment.collect {
glifLayout.post { udfpsEnrollView.updateGuidedEnrollment(it) }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.guidedEnrollmentSaved.collect {
glifLayout.post { udfpsEnrollView.onGuidedPointSaved(it) }
}
} }
} }
} }
@@ -175,35 +184,35 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
} }
private fun HeaderText.toResource(): Int { private fun HeaderText.toResource(): Int {
return when (this.stageViewModel) { return when (this.enrollStageModel) {
StageViewModel.Center, EnrollStageModel.Center,
StageViewModel.Guided, EnrollStageModel.Guided,
StageViewModel.Fingertip, EnrollStageModel.Fingertip,
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
} }
} }
private fun DescriptionText.toResource(): Int? { private fun DescriptionText.toResource(): Int? {
return when (this.stageViewModel) { return when (this.enrollStageModel) {
StageViewModel.Center, EnrollStageModel.Center,
StageViewModel.Guided, EnrollStageModel.Guided,
StageViewModel.Fingertip, EnrollStageModel.Fingertip,
StageViewModel.LeftEdge, EnrollStageModel.LeftEdge,
StageViewModel.RightEdge -> null EnrollStageModel.RightEdge -> null
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
} }
} }
private fun EducationAnimationModel.toResource(): Int? { private fun EducationAnimationModel.toResource(): Int? {
return when (this.stageViewModel) { return when (this.enrollStageModel) {
StageViewModel.Center, EnrollStageModel.Center,
StageViewModel.Guided -> R.raw.udfps_center_hint_lottie EnrollStageModel.Guided -> R.raw.udfps_center_hint_lottie
StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie EnrollStageModel.Fingertip -> R.raw.udfps_tip_hint_lottie
StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie EnrollStageModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie EnrollStageModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
StageViewModel.Unknown -> null EnrollStageModel.Unknown -> null
} }
} }

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
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.model
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
/** Represents the description text for UDFPS enrollment */ /** Represents the description text for UDFPS enrollment */
data class DescriptionText( data class DescriptionText(
val isSuw: Boolean, val isSuw: Boolean,
val isAccessibility: Boolean, val isAccessibility: Boolean,
val stageViewModel: StageViewModel, val enrollStageModel: EnrollStageModel,
) )

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
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.model
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
/** Represents the header text for UDFPS enrollment */ /** Represents the header text for UDFPS enrollment */
data class HeaderText( data class HeaderText(
val isSuw: Boolean, val isSuw: Boolean,
val isAccessibility: Boolean, val isAccessibility: Boolean,
val stageViewModel: StageViewModel, val enrollStageModel: EnrollStageModel,
) )

View File

@@ -16,11 +16,11 @@
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 com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
/** Represents the lottie for UDFPS enrollment */ /** Represents the lottie for UDFPS enrollment */
data class EducationAnimationModel( data class EducationAnimationModel(
val isSuw: Boolean, val isSuw: Boolean,
val isAccessibility: Boolean, val isAccessibility: Boolean,
val stageViewModel: StageViewModel, val enrollStageModel: EnrollStageModel,
) )

View File

@@ -17,20 +17,24 @@
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.Point import android.graphics.Point
import android.graphics.PointF
import android.view.Surface import android.view.Surface
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository 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.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor 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.EnrollStageInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects 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.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor 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.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
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.FingerprintAction 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.FingerprintEnrollEnrollingViewModel
@@ -61,9 +65,11 @@ class UdfpsViewModel(
orientationInteractor: OrientationInteractor, orientationInteractor: OrientationInteractor,
backgroundViewModel: BackgroundViewModel, backgroundViewModel: BackgroundViewModel,
sensorRepository: FingerprintSensorRepository, sensorRepository: FingerprintSensorRepository,
udfpsEnrollInteractor: UdfpsEnrollInteractor,
) : ViewModel() { ) : ViewModel() {
private val isSetupWizard = flowOf(false) private val isSetupWizard = flowOf(false)
private var shouldResetErollment = false
private var _enrollState: Flow<FingerEnrollState?> = private var _enrollState: Flow<FingerEnrollState?> =
fingerprintEnrollEnrollingViewModel.enrollFlow fingerprintEnrollEnrollingViewModel.enrollFlow
@@ -112,6 +118,17 @@ class UdfpsViewModel(
} }
} }
/**
* This indicates at which point the UI should offset the fingerprint sensor icon for guided
* enrollment.
*/
val guidedEnrollment: Flow<PointF> =
udfpsEnrollInteractor.guidedEnrollmentOffset.distinctUntilChanged()
/** The saved version of [guidedEnrollment] */
val guidedEnrollmentSaved: Flow<PointF> =
guidedEnrollment.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
/** /**
* This is the saved progress, this is for when views are recreated and need saved state for the * This is the saved progress, this is for when views are recreated and need saved state for the
* first time. * first time.
@@ -132,13 +149,13 @@ class UdfpsViewModel(
} }
} }
/** Determines the current [StageViewModel] enrollment is in */ /** Determines the current [EnrollStageModel] enrollment is in */
val enrollStage: Flow<StageViewModel> = private val enrollStage: Flow<EnrollStageModel> =
combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event -> combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event ->
if (event is FingerEnrollState.EnrollProgress) { if (event is FingerEnrollState.EnrollProgress) {
val progress = val progress =
(event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired (event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired
var stageToReturn: StageViewModel = StageViewModel.Center var stageToReturn: EnrollStageModel = EnrollStageModel.Center
thresholds.forEach { (threshold, stage) -> thresholds.forEach { (threshold, stage) ->
if (progress < threshold) { if (progress < threshold) {
return@forEach return@forEach
@@ -153,6 +170,40 @@ class UdfpsViewModel(
.filterNotNull() .filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 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 {
enrollStage.collect {
udfpsEnrollInteractor.updateGuidedEnrollment(it is EnrollStageModel.Guided)
}
}
viewModelScope.launch {
enrollState.filterIsInstance<FingerEnrollState.EnrollProgress>().collect {
udfpsEnrollInteractor.onEnrollmentStep(it.remainingSteps, it.totalStepsRequired)
}
}
viewModelScope.launch {
backgroundViewModel.background.filter { true }.collect { didGoToBackground() }
}
}
/** Indicates if we should show the lottie. */ /** Indicates if we should show the lottie. */
val shouldShowLottie: Flow<Boolean> = val shouldShowLottie: Flow<Boolean> =
combine( combine(
@@ -183,7 +234,7 @@ class UdfpsViewModel(
} }
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
private val shouldClearDescriptionText = enrollStage.map { it is StageViewModel.Unknown } private val shouldClearDescriptionText = enrollStage.map { it is EnrollStageModel.Unknown }
/** The description text for UDFPS enrollment */ /** The description text for UDFPS enrollment */
val descriptionText: Flow<DescriptionText?> = val descriptionText: Flow<DescriptionText?> =
@@ -202,6 +253,10 @@ class UdfpsViewModel(
/** Indicates if the consumer is ready for enrollment */ /** Indicates if the consumer is ready for enrollment */
fun readyForEnrollment() { fun readyForEnrollment() {
if (shouldResetErollment) {
shouldResetErollment = false
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
}
fingerprintEnrollEnrollingViewModel.canEnroll() fingerprintEnrollEnrollingViewModel.canEnroll()
} }
@@ -237,8 +292,12 @@ class UdfpsViewModel(
} }
private fun doReset() { private fun doReset() {
/** Indicates if the icon should be animating or not */
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow _enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
progressSaved =
enrollState
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
} }
/** The lottie that should be shown for UDFPS Enrollment */ /** The lottie that should be shown for UDFPS Enrollment */
@@ -272,6 +331,7 @@ class UdfpsViewModel(
private val orientationInteractor: OrientationInteractor, private val orientationInteractor: OrientationInteractor,
private val backgroundViewModel: BackgroundViewModel, private val backgroundViewModel: BackgroundViewModel,
private val sensorRepository: FingerprintSensorRepository, private val sensorRepository: FingerprintSensorRepository,
private val udfpsEnrollInteractor: UdfpsEnrollInteractor,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -287,6 +347,7 @@ class UdfpsViewModel(
orientationInteractor, orientationInteractor,
backgroundViewModel, backgroundViewModel,
sensorRepository, sensorRepository,
udfpsEnrollInteractor,
) )
as T as T
} }

View File

@@ -1,89 +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.widget
import android.content.Context
import android.graphics.PointF
import android.util.TypedValue
import android.view.accessibility.AccessibilityManager
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) {
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 {
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
}
val 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
}
}

View File

@@ -24,6 +24,7 @@ import android.graphics.Canvas
import android.graphics.ColorFilter import android.graphics.ColorFilter
import android.graphics.Paint import android.graphics.Paint
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@@ -37,7 +38,6 @@ import androidx.core.animation.addListener
import androidx.core.graphics.toRect import androidx.core.graphics.toRect
import androidx.core.graphics.toRectF import androidx.core.graphics.toRectF
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
import kotlin.math.sin import kotlin.math.sin
/** /**
@@ -51,11 +51,11 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
private val fingerprintDrawable: ShapeDrawable private val fingerprintDrawable: ShapeDrawable
private val sensorOutlinePaint: Paint private val sensorOutlinePaint: Paint
private val blueFill: Paint private val blueFill: Paint
private val helper = UdfpsEnrollHelperV2(context)
@ColorInt private var enrollIconColor = 0 @ColorInt private var enrollIconColor = 0
@ColorInt private var movingTargetFill = 0 @ColorInt private var movingTargetFill = 0
private var currentScale = 1.0f private var currentScale = 1.0f
private var alpha = 0 private var alpha = 0
private var guidedEnrollmentOffset: PointF? = null
/** /**
* This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt] * This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt]
@@ -143,45 +143,6 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
invalidateSelf() invalidateSelf()
} }
/** Update the progress of the icon */
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()
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)
val shouldAnimateMovement =
!currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f
if (shouldAnimateMovement) {
targetAnimatorSet?.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()
}
/** Stop drawing the fingerprint icon. */ /** Stop drawing the fingerprint icon. */
fun stopDrawing() { fun stopDrawing() {
alpha = 0 alpha = 0
@@ -211,6 +172,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
if (currentBounds.equals(offsetRect)) { if (currentBounds.equals(offsetRect)) {
return return
} }
val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left) val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left)
xAnimator.addUpdateListener { xAnimator.addUpdateListener {
currX = it.animatedValue as Float currX = it.animatedValue as Float
@@ -260,6 +222,40 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
targetAnimationDuration = TARGET_ANIM_DURATION_LONG targetAnimationDuration = TARGET_ANIM_DURATION_LONG
} }
/**
* Indicates a change to guided enrollment has occurred. Also indicates if we are recreating the
* view, in which case their is no need to animate the icon to whatever position it was in.
*/
fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
guidedEnrollmentOffset = point
if (isRecreating) {
setAnimationTimeToZero()
} else {
restoreAnimationTime()
}
val currentBounds = getCurrLocation().toRect()
val offset = guidedEnrollmentOffset
if (offset?.x != 0f && offset?.y != 0f) {
val targetRect = Rect(sensorRectBounds).toRectF()
// 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.
targetRect.offset(offset!!.x, offset!!.y)
val shouldAnimateMovement = !currentBounds.equals(targetRect)
if (shouldAnimateMovement) {
targetAnimatorSet?.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)
}
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
invalidateSelf()
}
companion object { companion object {
private const val TAG = "UdfpsEnrollDrawableV2" private const val TAG = "UdfpsEnrollDrawableV2"
private const val DEFAULT_STROKE_WIDTH = 3f private const val DEFAULT_STROKE_WIDTH = 3f

View File

@@ -27,10 +27,12 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.animation.addListener
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.graphics.toRectF import androidx.core.graphics.toRectF
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
@@ -46,6 +48,7 @@ import kotlin.math.sin
class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) : class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) :
Drawable() { Drawable() {
private val sensorRect: Rect = Rect() private val sensorRect: Rect = Rect()
private var onFinishedCompletionAnimation: (() -> Unit)? = null
private var rotation: Int = 0 private var rotation: Int = 0
private val strokeWidthPx: Float private val strokeWidthPx: Float
@@ -287,6 +290,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkMarkDrawable.bounds = newBounds checkMarkDrawable.bounds = newBounds
checkMarkDrawable.setVisible(true, false) checkMarkDrawable.setVisible(true, false)
} }
doOnEnd {
onFinishedCompletionAnimation?.let{
it()
}
}
start() start()
} }
} }
@@ -380,6 +389,13 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS
} }
/**
* Indicates that the finish animation has completed, and enrollment can proceed to the next stage
*/
fun setFinishAnimationCompleted(onFinishedAnimation: () -> Unit) {
this.onFinishedCompletionAnimation = onFinishedAnimation
}
companion object { companion object {
private const val TAG = "UdfpsProgressBar" private const val TAG = "UdfpsProgressBar"
private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L

View File

@@ -18,6 +18,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
import android.content.Context import android.content.Context
import android.graphics.Point import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log import android.util.Log
@@ -31,7 +32,6 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import com.android.settings.R import com.android.settings.R
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.StageViewModel
import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -53,6 +53,13 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
private val udfpsUtils: UdfpsUtils = UdfpsUtils() private val udfpsUtils: UdfpsUtils = UdfpsUtils()
private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer
private var isRecreating = false private var isRecreating = false
private var onFinishedCompletionAnimation: (() -> Unit)? = null
init {
fingerprintProgressDrawable.setFinishAnimationCompleted {
onFinishedCompletionAnimation?.let { it() }
}
}
/** /**
* This function computes the center (x,y) location with respect to the parent [FrameLayout] for * This function computes the center (x,y) location with respect to the parent [FrameLayout] for
@@ -112,11 +119,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils) touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
} }
/** Updates the current enrollment stage. */
fun updateStage(it: StageViewModel) {
fingerprintIcon.updateStage(it)
}
/** Receive enroll progress event */ /** Receive enroll progress event */
fun onUdfpsEvent(event: FingerEnrollState) { fun onUdfpsEvent(event: FingerEnrollState) {
when (event) { when (event) {
@@ -174,7 +176,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
/** Receive enroll progress event */ /** Receive enroll progress event */
private fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { private fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
fingerprintIcon.onEnrollmentProgress(remaining, totalSteps)
fingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps) fingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps)
} }
@@ -241,10 +242,25 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
/** Indicates we should should restore the views saved state. */ /** Indicates we should should restore the views saved state. */
fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) { fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) {
fingerprintIcon.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true) fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
} }
/** Indicates we are recreating the UI from a saved state. */
fun onGuidedPointSaved(it: PointF) {
fingerprintIcon.updateGuidedEnrollment(it, true)
}
/**
* Indicates that the finish animation has completed, and enrollment can proceed to the next stage
*/
fun setFinishAnimationCompleted(onFinishedAnimation: () -> Unit) {
this.onFinishedCompletionAnimation = onFinishedAnimation
}
fun updateGuidedEnrollment(point: PointF) {
fingerprintIcon.updateGuidedEnrollment(point, false)
}
companion object { companion object {
private const val TAG = "UdfpsEnrollView" private const val TAG = "UdfpsEnrollView"
} }

View File

@@ -88,7 +88,10 @@ sealed interface FingerprintNavigationStep {
} }
/** UiSteps should have a 1 to 1 mapping between each screen of FingerprintEnrollment */ /** UiSteps should have a 1 to 1 mapping between each screen of FingerprintEnrollment */
sealed class UiStep : FingerprintNavigationStep sealed class UiStep(
val enterTransition: Transition = Transition.EnterFromRight,
val exitTransition: Transition = Transition.ExitToLeft,
) : FingerprintNavigationStep
/** This is the landing page for enrollment, where no content is shown. */ /** This is the landing page for enrollment, where no content is shown. */
data object Init : UiStep() { data object Init : UiStep() {
@@ -103,7 +106,7 @@ sealed interface FingerprintNavigationStep {
} else if (state.flowType is FastEnroll) { } else if (state.flowType is FastEnroll) {
TransitionStep(Enrollment(state.fingerprintSensor!!)) TransitionStep(Enrollment(state.fingerprintSensor!!))
} else { } else {
TransitionStep(Introduction) TransitionStep(Introduction())
} }
} }
else -> null else -> null
@@ -118,7 +121,7 @@ sealed interface FingerprintNavigationStep {
action: FingerprintAction, action: FingerprintAction,
): FingerprintNavigationStep? { ): FingerprintNavigationStep? {
return when (action) { return when (action) {
FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction) FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction())
FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null) FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null)
else -> null else -> null
} }
@@ -126,7 +129,10 @@ sealed interface FingerprintNavigationStep {
} }
/** Indicates the FingerprintIntroduction screen is being presented to the user */ /** Indicates the FingerprintIntroduction screen is being presented to the user */
data object Introduction : UiStep() { class Introduction(
enterTransition: Transition = Transition.EnterFromRight,
exitTransition: Transition = Transition.ExitToLeft,
) : UiStep(enterTransition, exitTransition) {
override fun update( override fun update(
state: NavigationState, state: NavigationState,
action: FingerprintAction, action: FingerprintAction,
@@ -141,7 +147,11 @@ sealed interface FingerprintNavigationStep {
} }
/** Indicates the FingerprintEducation screen is being presented to the user */ /** Indicates the FingerprintEducation screen is being presented to the user */
data class Education(val sensor: FingerprintSensor) : UiStep() { class Education(
val sensor: FingerprintSensor,
enterTransition: Transition = Transition.EnterFromRight,
exitTransition: Transition = Transition.ExitToLeft,
) : UiStep(enterTransition, exitTransition) {
override fun update( override fun update(
state: NavigationState, state: NavigationState,
action: FingerprintAction, action: FingerprintAction,
@@ -149,7 +159,8 @@ sealed interface FingerprintNavigationStep {
return when (action) { return when (action) {
FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!)) FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!))
FingerprintAction.NEGATIVE_BUTTON_PRESSED, FingerprintAction.NEGATIVE_BUTTON_PRESSED,
FingerprintAction.PREV -> TransitionStep(Introduction) FingerprintAction.PREV ->
TransitionStep(Introduction(Transition.EnterFromLeft, Transition.ExitToRight))
else -> null else -> null
} }
} }
@@ -179,7 +190,10 @@ sealed interface FingerprintNavigationStep {
): FingerprintNavigationStep? { ): FingerprintNavigationStep? {
return when (action) { return when (action) {
FingerprintAction.NEXT -> Finish(null) FingerprintAction.NEXT -> Finish(null)
FingerprintAction.PREV -> TransitionStep(Education(state.fingerprintSensor!!)) FingerprintAction.PREV ->
TransitionStep(
Education(state.fingerprintSensor!!, Transition.EnterFromLeft, Transition.ExitToRight)
)
FingerprintAction.ADD_ANOTHER -> TransitionStep(Enrollment(state.fingerprintSensor!!)) FingerprintAction.ADD_ANOTHER -> TransitionStep(Enrollment(state.fingerprintSensor!!))
else -> null else -> null
} }

View File

@@ -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.viewmodel
/** Indicates the type of transitions that can occur between fragments */
sealed class Transition {
/**
* Indicates the new fragment should slide in from the left side
*/
data object EnterFromLeft : Transition()
/**
* Indicates the new fragment should slide in from the right side
*/
data object EnterFromRight : Transition()
/**
* Indicates the old fragment should slide out to the left side
*/
data object ExitToLeft : Transition()
/**
* Indicates the old fragment should slide out to the right side
*/
data object ExitToRight : Transition()
}

View File

@@ -90,7 +90,7 @@ class FingerprintEnrollIntroFragmentTest {
private val navigationViewModel = private val navigationViewModel =
FingerprintNavigationViewModel( FingerprintNavigationViewModel(
Introduction, Introduction(),
false, false,
flowViewModel, flowViewModel,
interactor interactor

View File

@@ -28,7 +28,7 @@ import platform.test.screenshot.ViewScreenshotTestRule.Mode
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class FingerprintEnrollIntroScreenshotTest { class FingerprintEnrollIntroScreenshotTest {
private val injector: Injector = Injector(FingerprintNavigationStep.Introduction) private val injector: Injector = Injector(FingerprintNavigationStep.Introduction())
@Rule @Rule
@JvmField @JvmField