Merge "Implement FingerprintEnrollFindSensorV2Fragment" into main

This commit is contained in:
Hao Dong
2023-09-22 00:00:07 +00:00
committed by Android (Google) Code Review
9 changed files with 609 additions and 13 deletions

View File

@@ -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;
}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}