[BiometricsV2] Refactor EnrollIntroViewModel
1. Refactor FingerprintEnrollIntroViewModel to kotlin and replace LiveData as Flow 2. Set importantForAccessibility to sud_scroll_view (porting solution of b/244595576 into fingerprint enrollment v2) Bug: 286198097 Test: atest -m FingerprintEnrollIntroViewModelTest Test: atest -m FingerprintEnrollmentActivityTest Test: atest -m biometrics-enrollment-test Change-Id: Idd4e9d77d040d7efd61342284d7b6a493a20a539
This commit is contained in:
@@ -28,12 +28,15 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus
|
||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||
@@ -47,6 +50,8 @@ import com.google.android.setupdesign.template.RequireScrollMixin
|
||||
import com.google.android.setupdesign.util.DeviceHelper
|
||||
import com.google.android.setupdesign.util.DynamicColorPalette
|
||||
import com.google.android.setupdesign.util.DynamicColorPalette.ColorType.ACCENT
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
@@ -59,12 +64,7 @@ class FingerprintEnrollIntroFragment : Fragment() {
|
||||
|
||||
private var _viewModel: FingerprintEnrollIntroViewModel? = null
|
||||
private val viewModel: FingerprintEnrollIntroViewModel
|
||||
get() {
|
||||
if (_viewModel == null) {
|
||||
_viewModel = viewModelProvider[FingerprintEnrollIntroViewModel::class.java]
|
||||
}
|
||||
return _viewModel!!
|
||||
}
|
||||
get() = _viewModel!!
|
||||
|
||||
private var introView: GlifLayout? = null
|
||||
|
||||
@@ -73,10 +73,18 @@ class FingerprintEnrollIntroFragment : Fragment() {
|
||||
private var secondaryFooterButton: FooterButton? = null
|
||||
|
||||
private val onNextClickListener =
|
||||
View.OnClickListener { _: View? -> viewModel.onNextButtonClick() }
|
||||
View.OnClickListener { _: View? ->
|
||||
activity?.lifecycleScope?.let {
|
||||
viewModel.onNextButtonClick(it)
|
||||
}
|
||||
}
|
||||
|
||||
private val onSkipOrCancelClickListener =
|
||||
View.OnClickListener { _: View? -> viewModel.onSkipOrCancelButtonClick() }
|
||||
View.OnClickListener { _: View? ->
|
||||
activity?.lifecycleScope?.let {
|
||||
viewModel.onSkipOrCancelButtonClick(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -95,7 +103,7 @@ class FingerprintEnrollIntroFragment : Fragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
requireActivity().bindFingerprintEnrollIntroView(
|
||||
view = introView!!,
|
||||
canAssumeUdfps = viewModel.canAssumeUdfps(),
|
||||
canAssumeUdfps = viewModel.canAssumeUdfps,
|
||||
isBiometricUnlockDisabledByAdmin = viewModel.isBiometricUnlockDisabledByAdmin,
|
||||
isParentalConsentRequired = viewModel.isParentalConsentRequired,
|
||||
descriptionDisabledByAdminSupplier = { getDescriptionDisabledByAdmin(view.context) }
|
||||
@@ -105,9 +113,10 @@ class FingerprintEnrollIntroFragment : Fragment() {
|
||||
override fun onStart() {
|
||||
val context: Context = requireContext()
|
||||
val footerBarMixin: FooterBarMixin = footerBarMixin
|
||||
viewModel.updateEnrollableStatus(lifecycleScope)
|
||||
initPrimaryFooterButton(context, footerBarMixin)
|
||||
initSecondaryFooterButton(context, footerBarMixin)
|
||||
observePageStatusLiveDataIfNeed()
|
||||
collectPageStatusFlowIfNeed()
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
@@ -152,46 +161,41 @@ class FingerprintEnrollIntroFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun observePageStatusLiveDataIfNeed() {
|
||||
val statusLiveData: LiveData<FingerprintEnrollIntroStatus> =
|
||||
viewModel.pageStatusLiveData
|
||||
val status: FingerprintEnrollIntroStatus? = statusLiveData.value
|
||||
|
||||
if (DEBUG) {
|
||||
Log.e(
|
||||
TAG, "observePageStatusLiveDataIfNeed() requireScrollWithButton, status:"
|
||||
+ status
|
||||
)
|
||||
}
|
||||
|
||||
if (status != null && (status.hasScrollToBottom()
|
||||
|| status.enrollableStatus === FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
|
||||
) {
|
||||
// Update once and do not requireScrollWithButton() again when page has scrolled to
|
||||
// bottom or User has enrolled at least a fingerprint, because if we
|
||||
// requireScrollWithButton() again, primary button will become "More" after scrolling.
|
||||
updateFooterButtons(status)
|
||||
return
|
||||
}
|
||||
|
||||
introView!!.getMixin(RequireScrollMixin::class.java).let {
|
||||
it.requireScrollWithButton(
|
||||
requireActivity(),
|
||||
primaryFooterButton!!,
|
||||
moreButtonTextRes,
|
||||
onNextClickListener
|
||||
)
|
||||
it.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
|
||||
viewModel.setHasScrolledToBottom(!scrollNeeded)
|
||||
private fun collectPageStatusFlowIfNeed() {
|
||||
lifecycleScope.launch {
|
||||
val status = viewModel.pageStatusFlow.first()
|
||||
Log.d(TAG, "collectPageStatusFlowIfNeed status:$status")
|
||||
if (status.hasScrollToBottom()
|
||||
|| status.enrollableStatus === FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||
) {
|
||||
// Update once and do not requireScrollWithButton() again when page has
|
||||
// scrolled to bottom or User has enrolled at least a fingerprint, because if
|
||||
// we requireScrollWithButton() again, primary button will become "More" after
|
||||
// scrolling.
|
||||
updateFooterButtons(status)
|
||||
} else {
|
||||
introView!!.getMixin(RequireScrollMixin::class.java).let {
|
||||
it.requireScrollWithButton(
|
||||
requireActivity(),
|
||||
primaryFooterButton!!,
|
||||
moreButtonTextRes,
|
||||
onNextClickListener
|
||||
)
|
||||
it.setOnRequireScrollStateChangedListener { scrollNeeded: Boolean ->
|
||||
viewModel.setHasScrolledToBottom(!scrollNeeded, lifecycleScope)
|
||||
}
|
||||
}
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.pageStatusFlow.collect(
|
||||
this@FingerprintEnrollIntroFragment::updateFooterButtons
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
statusLiveData.observe(this) { newStatus: FingerprintEnrollIntroStatus ->
|
||||
updateFooterButtons(newStatus)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
_viewModel = null
|
||||
_viewModel = viewModelProvider[FingerprintEnrollIntroViewModel::class.java]
|
||||
super.onAttach(context)
|
||||
}
|
||||
|
||||
@@ -319,4 +323,7 @@ fun FragmentActivity.bindFingerprintEnrollIntroView(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
view.findViewById<ScrollView>(R.id.sud_scroll_view)?.importantForAccessibility =
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
}
|
||||
|
||||
@@ -68,11 +68,8 @@ import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishView
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FINGERPRINT_ENROLL_FINISH_ACTION_ADD_BUTTON_CLICK
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FINGERPRINT_ENROLL_FINISH_ACTION_NEXT_BUTTON_CLICK
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollFinishViewModel.FingerprintEnrollFinishAction
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FingerprintEnrollIntroAction
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollmentViewModel
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintErrorDialogSetResultAction.FINGERPRINT_ERROR_DIALOG_ACTION_SET_RESULT_FINISH
|
||||
@@ -129,13 +126,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
|
||||
private var isFirstFragmentAdded = false
|
||||
|
||||
private val introActionObserver: Observer<Int> = Observer<Int> { action ->
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "introActionObserver($action)")
|
||||
}
|
||||
action?.let { onIntroAction(it) }
|
||||
}
|
||||
|
||||
private val findSensorActionObserver: Observer<Int> = Observer<Int> { action ->
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "findSensorActionObserver($action)")
|
||||
@@ -290,12 +280,10 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
if (request.isSkipIntro || request.isSkipFindSensor) {
|
||||
return
|
||||
}
|
||||
introViewModel.let {
|
||||
// Clear ActionLiveData in FragmentViewModel to prevent getting previous action during
|
||||
// recreate, like press 'Agree' then press 'back' in FingerprintEnrollFindSensor
|
||||
// activity.
|
||||
it.clearActionLiveData()
|
||||
it.actionLiveData.observe(this, introActionObserver)
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
introViewModel.actionFlow.collect(this@FingerprintEnrollmentActivity::onIntroAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,23 +468,20 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onIntroAction(@FingerprintEnrollIntroAction action: Int) {
|
||||
private fun onIntroAction(action: FingerprintEnrollIntroAction) {
|
||||
Log.d(TAG, "onIntroAction($action)")
|
||||
when (action) {
|
||||
FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH -> {
|
||||
onSetActivityResult(
|
||||
ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null)
|
||||
)
|
||||
FingerprintEnrollIntroAction.DONE_AND_FINISH -> {
|
||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_FINISHED, null))
|
||||
return
|
||||
}
|
||||
|
||||
FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL -> {
|
||||
onSetActivityResult(
|
||||
ActivityResult(BiometricEnrollBase.RESULT_SKIP, null)
|
||||
)
|
||||
FingerprintEnrollIntroAction.SKIP_OR_CANCEL -> {
|
||||
onSetActivityResult(ActivityResult(BiometricEnrollBase.RESULT_SKIP, null))
|
||||
return
|
||||
}
|
||||
|
||||
FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL -> {
|
||||
FingerprintEnrollIntroAction.CONTINUE_ENROLL -> {
|
||||
startFindSensorFragment()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.biometrics2.ui.viewmodel;
|
||||
|
||||
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX;
|
||||
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK;
|
||||
import static com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_UNKNOWN;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus;
|
||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Fingerprint intro onboarding page view model implementation
|
||||
*/
|
||||
public class FingerprintEnrollIntroViewModel extends AndroidViewModel {
|
||||
|
||||
private static final String TAG = "FingerprintEnrollIntroViewModel";
|
||||
private static final boolean HAS_SCROLLED_TO_BOTTOM_DEFAULT = false;
|
||||
private static final FingerprintEnrollable ENROLLABLE_STATUS_DEFAULT =
|
||||
FINGERPRINT_ENROLLABLE_UNKNOWN;
|
||||
|
||||
/**
|
||||
* User clicks 'Done' button on this page
|
||||
*/
|
||||
public static final int FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH = 0;
|
||||
|
||||
/**
|
||||
* User clicks 'Agree' button on this page
|
||||
*/
|
||||
public static final int FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL = 1;
|
||||
|
||||
/**
|
||||
* User clicks 'Skip' button on this page
|
||||
*/
|
||||
public static final int FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL = 2;
|
||||
|
||||
@IntDef(prefix = { "FINGERPRINT_ENROLL_INTRO_ACTION_" }, value = {
|
||||
FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH,
|
||||
FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL,
|
||||
FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FingerprintEnrollIntroAction {}
|
||||
|
||||
@NonNull private final FingerprintRepository mFingerprintRepository;
|
||||
|
||||
private final MutableLiveData<Boolean> mHasScrolledToBottomLiveData =
|
||||
new MutableLiveData<>(HAS_SCROLLED_TO_BOTTOM_DEFAULT);
|
||||
private final MutableLiveData<FingerprintEnrollable> mEnrollableStatusLiveData =
|
||||
new MutableLiveData<>(ENROLLABLE_STATUS_DEFAULT);
|
||||
private final MediatorLiveData<FingerprintEnrollIntroStatus> mPageStatusLiveData =
|
||||
new MediatorLiveData<>();
|
||||
private final MutableLiveData<Integer> mActionLiveData = new MutableLiveData<>();
|
||||
private final int mUserId;
|
||||
@NonNull private final EnrollmentRequest mRequest;
|
||||
|
||||
public FingerprintEnrollIntroViewModel(@NonNull Application application,
|
||||
@NonNull FingerprintRepository fingerprintRepository,
|
||||
@NonNull EnrollmentRequest request, int userId) {
|
||||
super(application);
|
||||
mFingerprintRepository = fingerprintRepository;
|
||||
mRequest = request;
|
||||
mUserId = userId;
|
||||
|
||||
mPageStatusLiveData.addSource(
|
||||
mEnrollableStatusLiveData,
|
||||
enrollable -> {
|
||||
final Boolean toBottomValue = mHasScrolledToBottomLiveData.getValue();
|
||||
final FingerprintEnrollIntroStatus status = new FingerprintEnrollIntroStatus(
|
||||
toBottomValue != null ? toBottomValue : HAS_SCROLLED_TO_BOTTOM_DEFAULT,
|
||||
enrollable);
|
||||
mPageStatusLiveData.setValue(status);
|
||||
});
|
||||
mPageStatusLiveData.addSource(
|
||||
mHasScrolledToBottomLiveData,
|
||||
hasScrolledToBottom -> {
|
||||
final FingerprintEnrollable enrollableValue =
|
||||
mEnrollableStatusLiveData.getValue();
|
||||
final FingerprintEnrollIntroStatus status = new FingerprintEnrollIntroStatus(
|
||||
hasScrolledToBottom,
|
||||
enrollableValue != null ? enrollableValue : ENROLLABLE_STATUS_DEFAULT);
|
||||
mPageStatusLiveData.setValue(status);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enrollment request
|
||||
*/
|
||||
public EnrollmentRequest getRequest() {
|
||||
return mRequest;
|
||||
}
|
||||
|
||||
private void updateEnrollableStatus() {
|
||||
final int num = mFingerprintRepository.getNumOfEnrolledFingerprintsSize(mUserId);
|
||||
final int max =
|
||||
mRequest.isSuw() && !mRequest.isAfterSuwOrSuwSuggestedAction()
|
||||
? mFingerprintRepository.getMaxFingerprintsInSuw(getApplication().getResources())
|
||||
: mFingerprintRepository.getMaxFingerprints();
|
||||
mEnrollableStatusLiveData.postValue(num >= max
|
||||
? FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||
: FINGERPRINT_ENROLLABLE_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enrollable status and hasScrollToBottom live data
|
||||
*/
|
||||
public LiveData<FingerprintEnrollIntroStatus> getPageStatusLiveData() {
|
||||
updateEnrollableStatus();
|
||||
return mPageStatusLiveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear user's action live data
|
||||
*/
|
||||
public void clearActionLiveData() {
|
||||
mActionLiveData.setValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's action live data (like clicking Agree, Skip, or Done)
|
||||
*/
|
||||
public LiveData<Integer> getActionLiveData() {
|
||||
return mActionLiveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* The first sensor type is UDFPS sensor or not
|
||||
*/
|
||||
public boolean canAssumeUdfps() {
|
||||
return mFingerprintRepository.canAssumeUdfps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update onboarding intro page has scrolled to bottom
|
||||
*/
|
||||
public void setHasScrolledToBottom(boolean value) {
|
||||
mHasScrolledToBottomLiveData.postValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parental consent required or not during enrollment process
|
||||
*/
|
||||
public boolean isParentalConsentRequired() {
|
||||
return mFingerprintRepository.isParentalConsentRequired(getApplication());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fingerprint is disable by admin or not
|
||||
*/
|
||||
public boolean isBiometricUnlockDisabledByAdmin() {
|
||||
return mFingerprintRepository.isDisabledByAdmin(getApplication(), mUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicks next button
|
||||
*/
|
||||
public void onNextButtonClick() {
|
||||
final FingerprintEnrollable status = mEnrollableStatusLiveData.getValue();
|
||||
switch (status != null ? status : ENROLLABLE_STATUS_DEFAULT) {
|
||||
case FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX:
|
||||
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
|
||||
break;
|
||||
case FINGERPRINT_ENROLLABLE_OK:
|
||||
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "fail to click next, enrolled:" + status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicks skip/cancel button
|
||||
*/
|
||||
public void onSkipOrCancelButtonClick() {
|
||||
mActionLiveData.postValue(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.biometrics2.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollIntroStatus
|
||||
import com.android.settings.biometrics2.ui.model.FingerprintEnrollable
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.CONTINUE_ENROLL
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.DONE_AND_FINISH
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroAction.SKIP_OR_CANCEL
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Fingerprint intro onboarding page view model implementation */
|
||||
class FingerprintEnrollIntroViewModel(
|
||||
application: Application,
|
||||
private val fingerprintRepository: FingerprintRepository,
|
||||
val request: EnrollmentRequest,
|
||||
private val userId: Int
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
/** User's action flow (like clicking Agree, Skip, or Done) */
|
||||
private val _actionFlow = MutableSharedFlow<FingerprintEnrollIntroAction>()
|
||||
val actionFlow: SharedFlow<FingerprintEnrollIntroAction>
|
||||
get() = _actionFlow.asSharedFlow()
|
||||
|
||||
private fun getEnrollableStatus(): FingerprintEnrollable {
|
||||
val num = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
|
||||
val max =
|
||||
if (request.isSuw && !request.isAfterSuwOrSuwSuggestedAction)
|
||||
fingerprintRepository.getMaxFingerprintsInSuw(
|
||||
getApplication<Application>().resources
|
||||
)
|
||||
else
|
||||
fingerprintRepository.maxFingerprints
|
||||
return if (num >= max)
|
||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
|
||||
else
|
||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK
|
||||
}
|
||||
|
||||
private val hasScrolledToBottomFlow = MutableStateFlow(HAS_SCROLLED_TO_BOTTOM_DEFAULT)
|
||||
private val enrollableStatusFlow = MutableStateFlow(getEnrollableStatus())
|
||||
|
||||
/** Enrollable status and hasScrollToBottom live data */
|
||||
val pageStatusFlow: Flow<FingerprintEnrollIntroStatus> =
|
||||
hasScrolledToBottomFlow.combine(enrollableStatusFlow) {
|
||||
hasScrolledToBottom: Boolean, enrollableStatus: FingerprintEnrollable ->
|
||||
FingerprintEnrollIntroStatus(hasScrolledToBottom, enrollableStatus)
|
||||
}
|
||||
|
||||
fun updateEnrollableStatus(scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
enrollableStatusFlow.emit(getEnrollableStatus())
|
||||
}
|
||||
}
|
||||
|
||||
/** The first sensor type is UDFPS sensor or not */
|
||||
val canAssumeUdfps: Boolean
|
||||
get() = fingerprintRepository.canAssumeUdfps()
|
||||
|
||||
/** Update onboarding intro page has scrolled to bottom */
|
||||
fun setHasScrolledToBottom(value: Boolean, scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
hasScrolledToBottomFlow.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
/** Get parental consent required or not during enrollment process */
|
||||
val isParentalConsentRequired: Boolean
|
||||
get() = fingerprintRepository.isParentalConsentRequired(getApplication())
|
||||
|
||||
/** Get fingerprint is disable by admin or not */
|
||||
val isBiometricUnlockDisabledByAdmin: Boolean
|
||||
get() = fingerprintRepository.isDisabledByAdmin(getApplication(), userId)
|
||||
|
||||
/**
|
||||
* User clicks next button
|
||||
*/
|
||||
fun onNextButtonClick(scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
when (val status = enrollableStatusFlow.value) {
|
||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX ->
|
||||
_actionFlow.emit(DONE_AND_FINISH)
|
||||
|
||||
FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK ->
|
||||
_actionFlow.emit(CONTINUE_ENROLL)
|
||||
|
||||
else -> Log.w(TAG, "fail to click next, enrolled:$status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** User clicks skip/cancel button */
|
||||
fun onSkipOrCancelButtonClick(scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
_actionFlow.emit(SKIP_OR_CANCEL)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FingerprintEnrollIntroViewModel"
|
||||
private const val HAS_SCROLLED_TO_BOTTOM_DEFAULT = false
|
||||
private val ENROLLABLE_STATUS_DEFAULT = FingerprintEnrollable.FINGERPRINT_ENROLLABLE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
enum class FingerprintEnrollIntroAction {
|
||||
/** User clicks 'Done' button on this page */
|
||||
DONE_AND_FINISH,
|
||||
/** User clicks 'Agree' button on this page */
|
||||
CONTINUE_ENROLL,
|
||||
/** User clicks 'Skip' button on this page */
|
||||
SKIP_OR_CANCEL
|
||||
}
|
||||
Reference in New Issue
Block a user