Merge "Implement FingerprintEnrollFindSensorV2Fragment" into main
This commit is contained in:
@@ -30,10 +30,10 @@ import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics.BiometricEnrollBase;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
/** Fingerprint error dialog, will be shown when an error occurs during fingerprint enrollment. */
|
||||
@@ -95,7 +95,7 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment {
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
@@ -20,11 +20,13 @@ import android.annotation.ColorInt
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
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.FingerprintEnrollFindSensorV2Fragment
|
||||
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.Education
|
||||
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.FingerprintEnrollViewModel
|
||||
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.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.Intro
|
||||
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.ChooseLockSettingsHelper
|
||||
import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
|
||||
@@ -72,6 +78,10 @@ private const val TAG = "FingerprintEnrollmentV2Activity"
|
||||
class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
private lateinit var navigationViewModel: FingerprintEnrollNavigationViewModel
|
||||
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
|
||||
|
||||
/** Result listener for ChooseLock activity flow. */
|
||||
@@ -94,6 +104,11 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
super.onAttachedToWindow()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
foldStateViewModel.onConfigurationChange(newConfig)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
private fun getBackgroundColor(): Int {
|
||||
val stateList: ColorStateList? =
|
||||
@@ -178,16 +193,53 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
)
|
||||
)[FingerprintEnrollNavigationViewModel::class.java]
|
||||
|
||||
// Initialize FoldStateViewModel
|
||||
foldStateViewModel =
|
||||
ViewModelProvider(this, FoldStateViewModel.FoldStateViewModelFactory(context))[
|
||||
FoldStateViewModel::class.java]
|
||||
foldStateViewModel.onConfigurationChange(resources.configuration)
|
||||
|
||||
// Initialize FingerprintViewModel
|
||||
ViewModelProvider(
|
||||
this,
|
||||
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(interactor, backgroundDispatcher)
|
||||
)[FingerprintEnrollViewModel::class.java]
|
||||
fingerprintEnrollViewModel =
|
||||
ViewModelProvider(
|
||||
this,
|
||||
FingerprintEnrollViewModel.FingerprintEnrollViewModelFactory(
|
||||
interactor,
|
||||
backgroundDispatcher
|
||||
)
|
||||
)[FingerprintEnrollViewModel::class.java]
|
||||
|
||||
// Initialize scroll view model
|
||||
ViewModelProvider(this, FingerprintScrollViewModel.FingerprintScrollViewModelFactory())[
|
||||
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 {
|
||||
navigationViewModel.navigationViewModel.filterNotNull().collect {
|
||||
Log.d(TAG, "navigationStep $it")
|
||||
|
@@ -19,10 +19,11 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollNavigationViewModel
|
||||
|
||||
/** 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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@@ -17,26 +17,197 @@
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment
|
||||
|
||||
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.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
* 1. Inform the user where the fingerprint sensor is on their device
|
||||
* 2. Explain to the user how the enrollment process shown by [FingerprintEnrollEnrollingV2Fragment]
|
||||
* will work.
|
||||
*/
|
||||
class FingerprintEnrollFindSensorV2Fragment : 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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
val navigationViewModel =
|
||||
ViewModelProvider(requireActivity())[FingerprintEnrollNavigationViewModel::class.java]
|
||||
viewModel =
|
||||
ViewModelProvider(requireActivity())[FingerprintEnrollFindSensorViewModel::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
|
||||
// Next button responsible for starting the next fragment.
|
||||
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)
|
||||
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