Merge "Implement FingerprintEnrollFindSensorV2Fragment" into main
This commit is contained in:
@@ -30,10 +30,10 @@ import android.hardware.fingerprint.FingerprintManager;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase;
|
|
||||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||||
|
|
||||||
/** Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment. */
|
/** Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment. */
|
||||||
@@ -95,7 +95,7 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showErrorDialog(BiometricEnrollBase host, int errMsgId, boolean isSetup) {
|
public static void showErrorDialog(FragmentActivity host, int errMsgId, boolean isSetup) {
|
||||||
if (host.isFinishing()) {
|
if (host.isFinishing()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -20,11 +20,13 @@ import android.annotation.ColorInt
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
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
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
@@ -44,17 +46,21 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.Finge
|
|||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollEnrollingV2Fragment
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollFindSensorV2Fragment
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.AccessibilityViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Confirmation
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Education
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Enrollment
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollViewModel
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintGatekeeperViewModel
|
||||||
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.Finish
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Finish
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FoldStateViewModel
|
||||||
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.Intro
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Intro
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.LaunchConfirmDeviceCredential
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.LaunchConfirmDeviceCredential
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.OrientationStateViewModel
|
||||||
import com.android.settings.password.ChooseLockGeneric
|
import com.android.settings.password.ChooseLockGeneric
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper
|
import com.android.settings.password.ChooseLockSettingsHelper
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
||||||
@@ -72,6 +78,10 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
|
|||||||
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||||
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
|
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
|
||||||
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
private lateinit var gatekeeperViewModel: FingerprintGatekeeperViewModel
|
||||||
|
private lateinit var fingerprintEnrollViewModel: FingerprintEnrollViewModel
|
||||||
|
private lateinit var accessibilityViewModel: AccessibilityViewModel
|
||||||
|
private lateinit var foldStateViewModel: FoldStateViewModel
|
||||||
|
private lateinit var orientationStateViewModel: OrientationStateViewModel
|
||||||
private val coroutineDispatcher = Dispatchers.Default
|
private val coroutineDispatcher = Dispatchers.Default
|
||||||
|
|
||||||
/** Result listener for ChooseLock activity flow. */
|
/** Result listener for ChooseLock activity flow. */
|
||||||
@@ -94,6 +104,11 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
|||||||
super.onAttachedToWindow()
|
super.onAttachedToWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
foldStateViewModel.onConfigurationChange(newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
private fun getBackgroundColor(): Int {
|
private fun getBackgroundColor(): Int {
|
||||||
val stateList: ColorStateList? =
|
val stateList: ColorStateList? =
|
||||||
@@ -178,16 +193,53 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
|||||||
)
|
)
|
||||||
)[FingerprintEnrollNavigationViewModel::class.java]
|
)[FingerprintEnrollNavigationViewModel::class.java]
|
||||||
|
|
||||||
|
// Initialize FoldStateViewModel
|
||||||
|
foldStateViewModel =
|
||||||
|
ViewModelProvider(this, FoldStateViewModel.FoldStateViewModelFactory(context))[
|
||||||
|
FoldStateViewModel::class.java]
|
||||||
|
foldStateViewModel.onConfigurationChange(resources.configuration)
|
||||||
|
|
||||||
// Initialize FingerprintViewModel
|
// Initialize FingerprintViewModel
|
||||||
ViewModelProvider(
|
fingerprintEnrollViewModel =
|
||||||
this,
|
ViewModelProvider(
|
||||||
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor, backgroundDispatcher)
|
this,
|
||||||
)[FingerprintEnrollViewModel::class.java]
|
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
|
||||||
|
interactor,
|
||||||
|
backgroundDispatcher
|
||||||
|
)
|
||||||
|
)[FingerprintEnrollViewModel::class.java]
|
||||||
|
|
||||||
// Initialize scroll view model
|
// Initialize scroll view model
|
||||||
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
|
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
|
||||||
FingerprintScrollViewModel::class.java]
|
FingerprintScrollViewModel::class.java]
|
||||||
|
|
||||||
|
// Initialize AccessibilityViewModel
|
||||||
|
accessibilityViewModel =
|
||||||
|
ViewModelProvider(
|
||||||
|
this,
|
||||||
|
AccessibilityViewModel.AccessibilityViewModelFactory(
|
||||||
|
getSystemService(AccessibilityManager::class.java)!!
|
||||||
|
)
|
||||||
|
)[AccessibilityViewModel::class.java]
|
||||||
|
|
||||||
|
// Initialize OrientationViewModel
|
||||||
|
orientationStateViewModel =
|
||||||
|
ViewModelProvider(this, OrientationStateViewModel.OrientationViewModelFactory(context))[
|
||||||
|
OrientationStateViewModel::class.java]
|
||||||
|
|
||||||
|
// Initialize FingerprintEnrollFindSensorViewModel
|
||||||
|
ViewModelProvider(
|
||||||
|
this,
|
||||||
|
FingerprintEnrollFindSensorViewModel.FingerprintEnrollFindSensorViewModelFactory(
|
||||||
|
navigationViewModel,
|
||||||
|
fingerprintEnrollViewModel,
|
||||||
|
gatekeeperViewModel,
|
||||||
|
accessibilityViewModel,
|
||||||
|
foldStateViewModel,
|
||||||
|
orientationStateViewModel
|
||||||
|
)
|
||||||
|
)[FingerprintEnrollFindSensorViewModel::class.java]
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
navigationViewModel.navigationViewModel.filterNotNull().collect {
|
navigationViewModel.navigationViewModel.filterNotNull().collect {
|
||||||
Log.d(TAG, "navigationStep $it")
|
Log.d(TAG, "navigationStep $it")
|
||||||
|
@@ -19,10 +19,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.settings.R
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
|
||||||
|
|
||||||
/** A fragment that is responsible for enrolling a users fingerprint. */
|
/** A fragment that is responsible for enrolling a users fingerprint. */
|
||||||
class FingerprintEnrollEnrollingV2Fragment : Fragment() {
|
class FingerprintEnrollEnrollingV2Fragment : Fragment(R.layout.fingerprint_enroll_enrolling) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@@ -17,26 +17,197 @@
|
|||||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Surface
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.airbnb.lottie.LottieAnimationView
|
||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
|
import com.android.settings.biometrics.fingerprint.FingerprintErrorDialog
|
||||||
|
import com.android.settings.biometrics.fingerprint.FingerprintFindSensorAnimation
|
||||||
|
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollFindSensorViewModel
|
||||||
|
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||||
|
import com.google.android.setupcompat.template.FooterBarMixin
|
||||||
|
import com.google.android.setupcompat.template.FooterButton
|
||||||
|
import com.google.android.setupdesign.GlifLayout
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private const val TAG = "FingerprintEnrollFindSensorV2Fragment"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment that is used to educate the user about the fingerprint sensor on this device.
|
* A fragment that is used to educate the user about the fingerprint sensor on this device.
|
||||||
*
|
*
|
||||||
|
* If the sensor is not a udfps sensor, this fragment listens to fingerprint enrollment for
|
||||||
|
* proceeding to the enroll enrolling.
|
||||||
|
*
|
||||||
* The main goals of this page are
|
* The main goals of this page are
|
||||||
* 1. Inform the user where the fingerprint sensor is on their device
|
* 1. Inform the user where the fingerprint sensor is on their device
|
||||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||||
* will work.
|
* will work.
|
||||||
*/
|
*/
|
||||||
class FingerprintEnrollFindSensorV2Fragment : Fragment(R.layout.fingerprint_v2_enroll_find_sensor) {
|
class FingerprintEnrollFindSensorV2Fragment : Fragment() {
|
||||||
|
// This is only for non-udfps or non-sfps sensor. For udfps and sfps, we show lottie.
|
||||||
|
private var animation: FingerprintFindSensorAnimation? = null
|
||||||
|
|
||||||
|
private var contentLayoutId: Int = -1
|
||||||
|
private lateinit var viewModel: FingerprintEnrollFindSensorViewModel
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (savedInstanceState == null) {
|
viewModel =
|
||||||
val navigationViewModel =
|
ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::class.java]
|
||||||
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
|
lifecycleScope.launch {
|
||||||
|
viewModel.sensorType.collect {
|
||||||
|
contentLayoutId =
|
||||||
|
when (it) {
|
||||||
|
FingerprintSensorType.UDFPS_OPTICAL,
|
||||||
|
FingerprintSensorType.UDFPS_ULTRASONIC -> R.layout.udfps_enroll_find_sensor_layout
|
||||||
|
FingerprintSensorType.POWER_BUTTON -> R.layout.sfps_enroll_find_sensor_layout
|
||||||
|
else -> R.layout.fingerprint_v2_enroll_find_sensor
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(contentLayoutId, container, false).also { it ->
|
||||||
|
val view = it!! as GlifLayout
|
||||||
|
|
||||||
|
// Set up header and description
|
||||||
|
lifecycleScope.launch { viewModel.sensorType.collect { setTexts(it, view) } }
|
||||||
|
|
||||||
|
// Set up footer bar
|
||||||
|
val footerBarMixin = view.getMixin(FooterBarMixin::class.java)
|
||||||
|
setupSecondaryButton(footerBarMixin)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.showPrimaryButton.collect { setupPrimaryButton(footerBarMixin) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up lottie or animation
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.showSfpsLottie.collect { (isFolded, rotation) ->
|
||||||
|
setupLottie(view, getSfpsIllustrationLottieAnimation(isFolded, rotation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.showUdfpsLottie.collect { isAccessibilityEnabled ->
|
||||||
|
val lottieAnimation =
|
||||||
|
if (isAccessibilityEnabled) R.raw.udfps_edu_a11y_lottie else R.raw.udfps_edu_lottie
|
||||||
|
setupLottie(view, lottieAnimation) { viewModel.proceedToEnrolling() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.showRfpsAnimation.collect {
|
||||||
|
animation = view.findViewById(R.id.fingerprint_sensor_location_animation)
|
||||||
|
animation!!.startAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.showErrorDialog.collect { (errMsgId, isSetup) ->
|
||||||
|
// TODO: Covert error dialog kotlin as well
|
||||||
|
FingerprintErrorDialog.showErrorDialog(requireActivity(), errMsgId, isSetup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
animation?.stopAnimation()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSecondaryButton(footerBarMixin: FooterBarMixin) {
|
||||||
|
footerBarMixin.secondaryButton =
|
||||||
|
FooterButton.Builder(requireActivity())
|
||||||
|
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||||
|
.setListener {
|
||||||
|
run {
|
||||||
|
// TODO: Show the dialog for suw
|
||||||
|
Log.d(TAG, "onSkipClicked")
|
||||||
|
// TODO: Finish activity in the root activity instead.
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||||
|
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPrimaryButton(footerBarMixin: FooterBarMixin) {
|
||||||
|
footerBarMixin.primaryButton =
|
||||||
|
FooterButton.Builder(requireActivity())
|
||||||
|
.setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
|
||||||
|
.setListener {
|
||||||
|
run {
|
||||||
|
Log.d(TAG, "onStartButtonClick")
|
||||||
|
viewModel.proceedToEnrolling()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||||
|
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupLottie(
|
||||||
|
view: View,
|
||||||
|
lottieAnimation: Int,
|
||||||
|
lottieClickListener: View.OnClickListener? = null
|
||||||
|
) {
|
||||||
|
val illustrationLottie: LottieAnimationView? = view.findViewById(R.id.illustration_lottie)
|
||||||
|
illustrationLottie?.setAnimation(lottieAnimation)
|
||||||
|
illustrationLottie?.playAnimation()
|
||||||
|
illustrationLottie?.setOnClickListener(lottieClickListener)
|
||||||
|
illustrationLottie?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTexts(sensorType: FingerprintSensorType, view: GlifLayout) {
|
||||||
|
when (sensorType) {
|
||||||
|
FingerprintSensorType.UDFPS_OPTICAL,
|
||||||
|
FingerprintSensorType.UDFPS_ULTRASONIC -> {
|
||||||
|
view.setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title)
|
||||||
|
view.setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message)
|
||||||
|
}
|
||||||
|
FingerprintSensorType.POWER_BUTTON -> {
|
||||||
|
view.setHeaderText(R.string.security_settings_sfps_enroll_find_sensor_title)
|
||||||
|
view.setDescriptionText(R.string.security_settings_sfps_enroll_find_sensor_message)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
view.setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title)
|
||||||
|
view.setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSfpsIllustrationLottieAnimation(isFolded: Boolean, rotation: Int): Int {
|
||||||
|
val animation: Int
|
||||||
|
when (rotation) {
|
||||||
|
Surface.ROTATION_90 ->
|
||||||
|
animation =
|
||||||
|
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_left
|
||||||
|
else R.raw.fingerprint_edu_lottie_portrait_top_left)
|
||||||
|
Surface.ROTATION_180 ->
|
||||||
|
animation =
|
||||||
|
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_left
|
||||||
|
else R.raw.fingerprint_edu_lottie_landscape_bottom_left)
|
||||||
|
Surface.ROTATION_270 ->
|
||||||
|
animation =
|
||||||
|
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_bottom_right
|
||||||
|
else R.raw.fingerprint_edu_lottie_portrait_bottom_right)
|
||||||
|
else ->
|
||||||
|
animation =
|
||||||
|
(if (isFolded) R.raw.fingerprint_edu_lottie_folded_top_right
|
||||||
|
else R.raw.fingerprint_edu_lottie_landscape_top_right)
|
||||||
|
}
|
||||||
|
return animation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -180,7 +180,10 @@ class FingerprintEnrollIntroV2Fragment : Fragment(R.layout.fingerprint_v2_enroll
|
|||||||
scrollView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
scrollView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||||
// Next button responsible for starting the next fragment.
|
// Next button responsible for starting the next fragment.
|
||||||
val onNextButtonClick: View.OnClickListener =
|
val onNextButtonClick: View.OnClickListener =
|
||||||
View.OnClickListener { Log.d(TAG, "OnNextClicked") }
|
View.OnClickListener {
|
||||||
|
Log.d(TAG, "OnNextClicked")
|
||||||
|
navigationViewModel.nextStep()
|
||||||
|
}
|
||||||
|
|
||||||
val layout: GlifLayout = requireActivity().requireViewById(R.id.setup_wizard_layout)
|
val layout: GlifLayout = requireActivity().requireViewById(R.id.setup_wizard_layout)
|
||||||
footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
|
footerBarMixin = layout.getMixin(FooterBarMixin::class.java)
|
||||||
|
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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
|
||||||
|
|
||||||
|
import android.view.accessibility.AccessibilityManager
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
/** Represents all of the information on accessibility state. */
|
||||||
|
class AccessibilityViewModel(accessibilityManager: AccessibilityManager) : ViewModel() {
|
||||||
|
/** A flow that contains whether or not accessibility is enabled */
|
||||||
|
val isAccessibilityEnabled: Flow<Boolean> =
|
||||||
|
callbackFlow {
|
||||||
|
val listener =
|
||||||
|
AccessibilityManager.AccessibilityStateChangeListener { enabled -> trySend(enabled) }
|
||||||
|
accessibilityManager.addAccessibilityStateChangeListener(listener)
|
||||||
|
|
||||||
|
// This clause will be called when no one is listening to the flow
|
||||||
|
awaitClose { accessibilityManager.removeAccessibilityStateChangeListener(listener) }
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
viewModelScope, // This is going to tied to the view model scope
|
||||||
|
SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
class AccessibilityViewModelFactory(private val accessibilityManager: AccessibilityManager) :
|
||||||
|
ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
return AccessibilityViewModel(accessibilityManager) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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
|
||||||
|
|
||||||
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.transform
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** Models the UI state for [FingerprintEnrollFindSensorV2Fragment]. */
|
||||||
|
class FingerprintEnrollFindSensorViewModel(
|
||||||
|
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
|
||||||
|
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||||
|
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||||
|
accessibilityViewModel: AccessibilityViewModel,
|
||||||
|
foldStateViewModel: FoldStateViewModel,
|
||||||
|
orientationStateViewModel: OrientationStateViewModel
|
||||||
|
) : ViewModel() {
|
||||||
|
/** Represents the stream of sensor type. */
|
||||||
|
val sensorType: Flow<FingerprintSensorType> =
|
||||||
|
fingerprintEnrollViewModel.sensorType.filterWhenEducationIsShown()
|
||||||
|
private val _isUdfps: Flow<Boolean> =
|
||||||
|
sensorType.map {
|
||||||
|
it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
|
||||||
|
}
|
||||||
|
private val _isSfps: Flow<Boolean> = sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
|
||||||
|
private val _isRearSfps: Flow<Boolean> =
|
||||||
|
combineTransform(_isSfps, _isUdfps) { v1, v2 -> !v1 && !v2 }
|
||||||
|
|
||||||
|
/** Represents the stream of showing primary button. */
|
||||||
|
val showPrimaryButton: Flow<Boolean> = _isUdfps.transform { if (it) emit(true) }
|
||||||
|
|
||||||
|
/** Represents the stream of showing sfps lottie, Pair(isFolded, rotation). */
|
||||||
|
val showSfpsLottie: Flow<Pair<Boolean, Int>> =
|
||||||
|
combineTransform(
|
||||||
|
_isSfps,
|
||||||
|
foldStateViewModel.isFolded,
|
||||||
|
orientationStateViewModel.rotation,
|
||||||
|
) { isSfps, isFolded, rotation ->
|
||||||
|
if (isSfps) emit(Pair(isFolded, rotation))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents the stream of showing udfps lottie. */
|
||||||
|
val showUdfpsLottie: Flow<Boolean> =
|
||||||
|
combineTransform(
|
||||||
|
_isUdfps,
|
||||||
|
accessibilityViewModel.isAccessibilityEnabled,
|
||||||
|
) { isUdfps, isAccessibilityEnabled ->
|
||||||
|
if (isUdfps) emit(isAccessibilityEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Represents the stream of showing rfps animation. */
|
||||||
|
val showRfpsAnimation: Flow<Boolean> = _isRearSfps.transform { if (it) emit(true) }
|
||||||
|
|
||||||
|
private val _showErrorDialog: MutableStateFlow<Pair<Int, Boolean>?> = MutableStateFlow(null)
|
||||||
|
/** Represents the stream of showing error dialog. */
|
||||||
|
val showErrorDialog = _showErrorDialog.filterNotNull()
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Start or end enroll flow
|
||||||
|
viewModelScope.launch {
|
||||||
|
combine(
|
||||||
|
fingerprintEnrollViewModel.sensorType,
|
||||||
|
gatekeeperViewModel.hasValidGatekeeperInfo,
|
||||||
|
gatekeeperViewModel.gatekeeperInfo,
|
||||||
|
navigationViewModel.navigationViewModel
|
||||||
|
) { sensorType, hasValidGatekeeperInfo, gatekeeperInfo, navigationViewModel ->
|
||||||
|
val shouldStartEnroll =
|
||||||
|
navigationViewModel.currStep == Education &&
|
||||||
|
sensorType != FingerprintSensorType.UDFPS_OPTICAL &&
|
||||||
|
sensorType != FingerprintSensorType.UDFPS_ULTRASONIC &&
|
||||||
|
hasValidGatekeeperInfo
|
||||||
|
if (shouldStartEnroll) (gatekeeperInfo as GatekeeperInfo.GatekeeperPasswordInfo).token
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
.collect { token ->
|
||||||
|
if (token != null) {
|
||||||
|
fingerprintEnrollViewModel.startEnroll(token, EnrollReason.FindSensor)
|
||||||
|
} else {
|
||||||
|
fingerprintEnrollViewModel.stopEnroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enroll progress flow
|
||||||
|
viewModelScope.launch {
|
||||||
|
combine(
|
||||||
|
navigationViewModel.enrollType,
|
||||||
|
fingerprintEnrollViewModel.enrollFlow.filterNotNull()
|
||||||
|
) { enrollType, enrollFlow ->
|
||||||
|
Pair(enrollType, enrollFlow)
|
||||||
|
}
|
||||||
|
.collect { (enrollType, enrollFlow) ->
|
||||||
|
when (enrollFlow) {
|
||||||
|
// TODO: Cancel the enroll() when EnrollProgress is received instead of proceeding to
|
||||||
|
// Enrolling page. Otherwise Enrolling page will receive the EnrollError.
|
||||||
|
is FingerEnrollStateViewModel.EnrollProgress -> proceedToEnrolling()
|
||||||
|
is FingerEnrollStateViewModel.EnrollError -> {
|
||||||
|
val errMsgId = enrollFlow.errMsgId
|
||||||
|
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
|
||||||
|
proceedToEnrolling()
|
||||||
|
} else {
|
||||||
|
_showErrorDialog.update { Pair(errMsgId, enrollType == SetupWizard) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FingerEnrollStateViewModel.EnrollHelp -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Proceed to EnrollEnrolling page. */
|
||||||
|
fun proceedToEnrolling() {
|
||||||
|
navigationViewModel.nextStep()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If we decide to remove previous fragment from activity, then we don't need to check
|
||||||
|
// whether education is shown for the flows that are subscribed by
|
||||||
|
// [FingerprintEnrollFindSensorV2Fragment].
|
||||||
|
private fun <T> Flow<T>.filterWhenEducationIsShown() =
|
||||||
|
combineTransform(navigationViewModel.navigationViewModel) { value, navigationViewModel ->
|
||||||
|
if (navigationViewModel.currStep == Education) {
|
||||||
|
emit(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FingerprintEnrollFindSensorViewModelFactory(
|
||||||
|
private val navigationViewModel: FingerprintEnrollNavigationViewModel,
|
||||||
|
private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
|
||||||
|
private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
|
||||||
|
private val accessibilityViewModel: AccessibilityViewModel,
|
||||||
|
private val foldStateViewModel: FoldStateViewModel,
|
||||||
|
private val orientationStateViewModel: OrientationStateViewModel
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return FingerprintEnrollFindSensorViewModel(
|
||||||
|
navigationViewModel,
|
||||||
|
fingerprintEnrollViewModel,
|
||||||
|
gatekeeperViewModel,
|
||||||
|
accessibilityViewModel,
|
||||||
|
foldStateViewModel,
|
||||||
|
orientationStateViewModel
|
||||||
|
)
|
||||||
|
as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
|
||||||
|
import com.android.systemui.unfold.updates.FoldProvider
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
|
||||||
|
/** Represents all of the information on fold state. */
|
||||||
|
class FoldStateViewModel(context: Context) : ViewModel() {
|
||||||
|
|
||||||
|
private val screenSizeFoldProvider = ScreenSizeFoldProvider(context)
|
||||||
|
|
||||||
|
/** A flow that contains the fold state info */
|
||||||
|
val isFolded: Flow<Boolean> = callbackFlow {
|
||||||
|
val foldStateListener =
|
||||||
|
object : FoldProvider.FoldCallback {
|
||||||
|
override fun onFoldUpdated(isFolded: Boolean) {
|
||||||
|
trySend(isFolded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screenSizeFoldProvider.registerCallback(foldStateListener, context.mainExecutor)
|
||||||
|
awaitClose { screenSizeFoldProvider.unregisterCallback(foldStateListener) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onConfigurationChange(newConfig: Configuration) {
|
||||||
|
screenSizeFoldProvider.onConfigurationChange(newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FoldStateViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
return FoldStateViewModel(context) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.OrientationEventListener
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.android.internal.R
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
/** Represents all of the information on orientation state and rotation state. */
|
||||||
|
class OrientationStateViewModel(private val context: Context) : ViewModel() {
|
||||||
|
|
||||||
|
/** A flow that contains the orientation info */
|
||||||
|
val orientation: Flow<Int> = callbackFlow {
|
||||||
|
val orientationEventListener =
|
||||||
|
object : OrientationEventListener(context) {
|
||||||
|
override fun onOrientationChanged(orientation: Int) {
|
||||||
|
trySend(orientation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orientationEventListener.enable()
|
||||||
|
awaitClose { orientationEventListener.disable() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A flow that contains the rotation info */
|
||||||
|
val rotation: Flow<Int> =
|
||||||
|
callbackFlow {
|
||||||
|
val orientationEventListener =
|
||||||
|
object : OrientationEventListener(context) {
|
||||||
|
override fun onOrientationChanged(orientation: Int) {
|
||||||
|
trySend(getRotationFromDefault(context.display!!.rotation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orientationEventListener.enable()
|
||||||
|
awaitClose { orientationEventListener.disable() }
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
viewModelScope, // This is going to tied to the view model scope
|
||||||
|
SharingStarted.WhileSubscribed(), // When no longer subscribed, we removeTheListener
|
||||||
|
context.display!!.rotation
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getRotationFromDefault(rotation: Int): Int {
|
||||||
|
val isReverseDefaultRotation =
|
||||||
|
context.resources.getBoolean(R.bool.config_reverseDefaultRotation)
|
||||||
|
return if (isReverseDefaultRotation) {
|
||||||
|
(rotation + 1) % 4
|
||||||
|
} else {
|
||||||
|
rotation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrientationViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
modelClass: Class<T>,
|
||||||
|
): T {
|
||||||
|
return OrientationStateViewModel(context) as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user