[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
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.kt
index 2ba1df1..e7e1cc8 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollIntroFragment.kt
@@ -28,12 +28,15 @@
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.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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
)
)
}
+
+ view.findViewById<ScrollView>(R.id.sud_scroll_view)?.importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_YES
}
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
index 562b7dd..c295bb3 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
@@ -68,11 +68,8 @@
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 @@
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 @@
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 @@
}
}
- 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()
}
}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
deleted file mode 100644
index 5e9085a..0000000
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.kt b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.kt
new file mode 100644
index 0000000..98137b4
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModel.kt
@@ -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
+}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
deleted file mode 100644
index 12b860b..0000000
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * 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 static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
-
-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.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL;
-import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH;
-import static com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollIntroViewModel.FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL;
-import static com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newAllFalseRequest;
-import static com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwDeferredRequest;
-import static com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwPortalRequest;
-import static com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwRequest;
-import static com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwSuggestedActionFlowRequest;
-import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository;
-import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints;
-import static com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupSuwMaxFingerprintsEnrollable;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Application;
-import android.content.res.Resources;
-import android.hardware.fingerprint.FingerprintManager;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.MutableLiveData;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-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.testutils.InstantTaskExecutorRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@RunWith(AndroidJUnit4.class)
-public class FingerprintEnrollIntroViewModelTest {
-
- private static final int TEST_USER_ID = 33;
-
- @Rule public final MockitoRule mockito = MockitoJUnit.rule();
- @Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
-
- @Mock private Resources mResources;
- @Mock private FingerprintManager mFingerprintManager;
-
- private Application mApplication;
-
- private FingerprintEnrollIntroViewModel newFingerprintEnrollIntroViewModel(
- @NonNull FingerprintRepository fingerprintRepository,
- @NonNull EnrollmentRequest enrollmentRequest) {
- final FingerprintEnrollIntroViewModel viewModel =
- new FingerprintEnrollIntroViewModel(mApplication, fingerprintRepository,
- enrollmentRequest, TEST_USER_ID);
- // MediatorLiveData won't update itself unless observed
- viewModel.getPageStatusLiveData().observeForever(event -> {});
- return viewModel;
- }
-
- @Before
- public void setUp() {
- mApplication = ApplicationProvider.getApplicationContext();
- }
-
- @Test
- public void testPageStatusLiveDataDefaultValue() {
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newAllFalseRequest(mApplication));
- final FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.hasScrollToBottom()).isFalse();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
- }
-
- @Test
- public void testPageStatusLiveDataRefreshWhenRefetch() {
- final FingerprintRepository repository = newFingerprintRepository(mFingerprintManager,
- TYPE_UDFPS_OPTICAL, 1);
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- repository,
- newAllFalseRequest(mApplication));
- FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.hasScrollToBottom()).isFalse();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
-
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 1);
-
- // Refetch PageStatusLiveData
- status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.hasScrollToBottom()).isFalse();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
- }
-
- @Test
- public void testClearActionLiveData() {
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newAllFalseRequest(mApplication));
-
- final MutableLiveData<Integer> actionLiveData =
- (MutableLiveData<Integer>) viewModel.getActionLiveData();
- actionLiveData.postValue(1);
- assertThat(actionLiveData.getValue()).isEqualTo(1);
-
- viewModel.clearActionLiveData();
-
- assertThat(actionLiveData.getValue()).isNull();
- }
-
- @Test
- public void testGetEnrollmentRequest() {
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newAllFalseRequest(mApplication));
-
- assertThat(viewModel.getRequest()).isNotNull();
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusOk_isSuw() {
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 0);
- setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
-
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newIsSuwRequest(mApplication));
- final FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusReachMax_isSuw() {
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 1);
- setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
-
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newIsSuwRequest(mApplication));
- final FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusOk_isNotSuw() {
- testOnStartToUpdateEnrollableStatusOk(newAllFalseRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusReachMax_isNotSuw() {
- testOnStartToUpdateEnrollableStatusReachMax(newAllFalseRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusOk_isSuwDeferred() {
- testOnStartToUpdateEnrollableStatusOk(newIsSuwDeferredRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusReachMax_isSuwDeferred() {
- testOnStartToUpdateEnrollableStatusReachMax(newIsSuwDeferredRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusOk_isSuwPortal() {
- testOnStartToUpdateEnrollableStatusOk(newIsSuwPortalRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusReachMax_isSuwPortal() {
- testOnStartToUpdateEnrollableStatusReachMax(newIsSuwPortalRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusOk_isSuwSuggestedActionFlow() {
- testOnStartToUpdateEnrollableStatusOk(newIsSuwSuggestedActionFlowRequest(mApplication));
- }
-
- @Test
- public void testOnStartToUpdateEnrollableStatusReachMax_isSuwSuggestedActionFlow() {
- testOnStartToUpdateEnrollableStatusReachMax(
- newIsSuwSuggestedActionFlowRequest(mApplication));
- }
-
- private void testOnStartToUpdateEnrollableStatusOk(@NonNull EnrollmentRequest request) {
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 0);
-
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- request);
- FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
- }
-
- private void testOnStartToUpdateEnrollableStatusReachMax(@NonNull EnrollmentRequest request) {
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 5);
-
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- request);
- FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
- }
-
- @Test
- public void testIsParentalConsentRequired() {
- // We shall not mock FingerprintRepository, but
- // FingerprintRepository.isParentalConsentRequired() calls static method inside, we can't
- // mock static method
- final FingerprintRepository fingerprintRepository = mock(FingerprintRepository.class);
- final FingerprintEnrollIntroViewModel viewModel = new FingerprintEnrollIntroViewModel(
- mApplication, fingerprintRepository, newAllFalseRequest(mApplication),
- TEST_USER_ID);
-
- when(fingerprintRepository.isParentalConsentRequired(mApplication)).thenReturn(true);
- assertThat(viewModel.isParentalConsentRequired()).isEqualTo(true);
-
- when(fingerprintRepository.isParentalConsentRequired(mApplication)).thenReturn(false);
- assertThat(viewModel.isParentalConsentRequired()).isEqualTo(false);
- }
-
- @Test
- public void testIsBiometricUnlockDisabledByAdmin() {
- // We shall not mock FingerprintRepository, but
- // FingerprintRepository.isDisabledByAdmin() calls static method inside, we can't mock
- // static method
- final FingerprintRepository fingerprintRepository = mock(FingerprintRepository.class);
- final FingerprintEnrollIntroViewModel viewModel = new FingerprintEnrollIntroViewModel(
- mApplication, fingerprintRepository, newAllFalseRequest(mApplication),
- TEST_USER_ID);
-
- when(fingerprintRepository.isDisabledByAdmin(mApplication, TEST_USER_ID)).thenReturn(true);
- assertThat(viewModel.isBiometricUnlockDisabledByAdmin()).isEqualTo(true);
-
- when(fingerprintRepository.isDisabledByAdmin(mApplication, TEST_USER_ID)).thenReturn(false);
- assertThat(viewModel.isBiometricUnlockDisabledByAdmin()).isEqualTo(false);
- }
-
- @Test
- public void testSetHasScrolledToBottom() {
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newAllFalseRequest(mApplication));
-
- viewModel.setHasScrolledToBottom(true);
- FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.hasScrollToBottom()).isEqualTo(true);
-
- viewModel.setHasScrolledToBottom(false);
- status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.hasScrollToBottom()).isEqualTo(false);
- }
-
- @Test
- public void testOnNextButtonClick_enrollNext() {
- // Set latest status to FINGERPRINT_ENROLLABLE_OK
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 0);
- setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
-
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newIsSuwRequest(mApplication));
- FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_OK);
-
- // Perform click on `next`
- viewModel.onNextButtonClick();
-
- assertThat(viewModel.getActionLiveData().getValue())
- .isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_CONTINUE_ENROLL);
- }
-
- @Test
- public void testOnNextButtonClick_doneAndFinish() {
- // Set latest status to FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
- setupFingerprintEnrolledFingerprints(mFingerprintManager, TEST_USER_ID, 1);
- setupSuwMaxFingerprintsEnrollable(mApplication, mResources, 1);
-
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newIsSuwRequest(mApplication));
- FingerprintEnrollIntroStatus status = viewModel.getPageStatusLiveData().getValue();
- assertThat(status.getEnrollableStatus()).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX);
-
- // Perform click on `next`
- viewModel.onNextButtonClick();
-
- assertThat(viewModel.getActionLiveData().getValue())
- .isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_DONE_AND_FINISH);
- }
-
- @Test
- public void testOnSkipOrCancelButtonClick() {
- final FingerprintEnrollIntroViewModel viewModel = newFingerprintEnrollIntroViewModel(
- newFingerprintRepository(mFingerprintManager, TYPE_UDFPS_OPTICAL, 5),
- newAllFalseRequest(mApplication));
-
- viewModel.onSkipOrCancelButtonClick();
-
- assertThat(viewModel.getActionLiveData().getValue())
- .isEqualTo(FINGERPRINT_ENROLL_INTRO_ACTION_SKIP_OR_CANCEL);
- }
-}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.kt b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.kt
new file mode 100644
index 0000000..08e5ac3
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollIntroViewModelTest.kt
@@ -0,0 +1,377 @@
+/*
+ * 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.content.res.Resources
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
+import com.android.settings.biometrics2.ui.model.FingerprintEnrollable.FINGERPRINT_ENROLLABLE_OK
+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 com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newAllFalseRequest
+import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwDeferredRequest
+import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwPortalRequest
+import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwRequest
+import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwSuggestedActionFlowRequest
+import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository
+import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints
+import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupSuwMaxFingerprintsEnrollable
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidJUnit4::class)
+class FingerprintEnrollIntroViewModelTest {
+
+ @get:Rule val mockito = MockitoJUnit.rule()
+
+ @Mock private lateinit var resources: Resources
+ @Mock private lateinit var fingerprintManager: FingerprintManager
+
+ private var application: Application = ApplicationProvider.getApplicationContext()
+
+ private fun newFingerprintEnrollIntroViewModel(
+ fingerprintRepository: FingerprintRepository,
+ enrollmentRequest: EnrollmentRequest
+ ) = FingerprintEnrollIntroViewModel(
+ application,
+ fingerprintRepository,
+ enrollmentRequest,
+ TEST_USER_ID
+ )
+
+ @Before
+ fun setUp() {
+ application = ApplicationProvider.getApplicationContext()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testPageStatusFlowDefaultAndUpdate() = runTest {
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 1),
+ newAllFalseRequest(application)
+ )
+
+ val statusList = listOfPageStatusFlow(viewModel)
+
+ runCurrent()
+
+ // assert default values
+ assertThat(statusList.size).isEqualTo(1)
+ assertThat(statusList[0].hasScrollToBottom()).isFalse()
+ assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_OK)
+
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 1)
+ viewModel.updateEnrollableStatus(backgroundScope)
+ runCurrent()
+
+ // assert new updated value
+ assertThat(statusList.size).isEqualTo(2)
+ assertThat(statusList[1].hasScrollToBottom()).isFalse()
+ assertThat(statusList[1].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun testOnStartToUpdateEnrollableStatusOk_isSuw() = runTest {
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 0)
+ setupSuwMaxFingerprintsEnrollable(application, resources, 1)
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ newIsSuwRequest(application)
+ )
+
+ val statusList = listOfPageStatusFlow(viewModel)
+
+ runCurrent()
+
+ assertThat(statusList.size).isEqualTo(1)
+ assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_OK)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusReachMax_isSuw() = runTest {
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 1)
+ setupSuwMaxFingerprintsEnrollable(application, resources, 1)
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ newIsSuwRequest(application)
+ )
+
+ val statusList = listOfPageStatusFlow(viewModel)
+
+ runCurrent()
+
+ assertThat(statusList.size).isEqualTo(1)
+ assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusOk_isNotSuw() = runTest {
+ testOnStartToUpdateEnrollableStatusOk(newAllFalseRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusReachMax_isNotSuw() = runTest {
+ testOnStartToUpdateEnrollableStatusReachMax(newAllFalseRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusOk_isSuwDeferred() = runTest {
+ testOnStartToUpdateEnrollableStatusOk(newIsSuwDeferredRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusReachMax_isSuwDeferred() = runTest {
+ testOnStartToUpdateEnrollableStatusReachMax(newIsSuwDeferredRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusOk_isSuwPortal() = runTest {
+ testOnStartToUpdateEnrollableStatusOk(newIsSuwPortalRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusReachMax_isSuwPortal() = runTest {
+ testOnStartToUpdateEnrollableStatusReachMax(newIsSuwPortalRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusOk_isSuwSuggestedActionFlow() = runTest {
+ testOnStartToUpdateEnrollableStatusOk(newIsSuwSuggestedActionFlowRequest(application))
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnStartToUpdateEnrollableStatusReachMax_isSuwSuggestedActionFlow() = runTest {
+ testOnStartToUpdateEnrollableStatusReachMax(
+ newIsSuwSuggestedActionFlowRequest(application)
+ )
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun TestScope.testOnStartToUpdateEnrollableStatusOk(request: EnrollmentRequest) {
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 0)
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ request
+ )
+
+ val statusList = listOfPageStatusFlow(viewModel)
+
+ runCurrent()
+
+ assertThat(statusList.size).isEqualTo(1)
+ assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_OK)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun TestScope.testOnStartToUpdateEnrollableStatusReachMax(request: EnrollmentRequest) {
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 5)
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ request
+ )
+
+ val statusList = listOfPageStatusFlow(viewModel)
+
+ runCurrent()
+
+ assertThat(statusList.size).isEqualTo(1)
+ assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
+ }
+
+ @Test
+ fun testIsParentalConsentRequired() {
+ // We shall not mock FingerprintRepository, but
+ // FingerprintRepository.isParentalConsentRequired() calls static method inside, we can't
+ // mock static method
+ val fingerprintRepository = Mockito.mock(
+ FingerprintRepository::class.java
+ )
+ val viewModel = FingerprintEnrollIntroViewModel(
+ application,
+ fingerprintRepository,
+ newAllFalseRequest(application),
+ TEST_USER_ID
+ )
+ Mockito.`when`(
+ fingerprintRepository.isParentalConsentRequired(application)
+ ).thenReturn(true)
+ assertThat(viewModel.isParentalConsentRequired).isEqualTo(true)
+ Mockito.`when`(
+ fingerprintRepository.isParentalConsentRequired(application)
+ ).thenReturn(false)
+ assertThat(viewModel.isParentalConsentRequired).isEqualTo(false)
+ }
+
+ @Test
+ fun testIsBiometricUnlockDisabledByAdmin() {
+ // We shall not mock FingerprintRepository, but
+ // FingerprintRepository.isDisabledByAdmin() calls static method inside, we can't mock
+ // static method
+ val fingerprintRepository = Mockito.mock(FingerprintRepository::class.java)
+ val viewModel = FingerprintEnrollIntroViewModel(
+ application,
+ fingerprintRepository,
+ newAllFalseRequest(application),
+ TEST_USER_ID
+ )
+ Mockito.`when`(
+ fingerprintRepository.isDisabledByAdmin(application, TEST_USER_ID)
+ ).thenReturn(true)
+ assertThat(viewModel.isBiometricUnlockDisabledByAdmin).isEqualTo(true)
+ Mockito.`when`(
+ fingerprintRepository.isDisabledByAdmin(application, TEST_USER_ID)
+ ).thenReturn(false)
+ assertThat(viewModel.isBiometricUnlockDisabledByAdmin).isEqualTo(false)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testSetHasScrolledToBottom() = runTest {
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ newAllFalseRequest(application)
+ )
+
+ val pageStatusList = listOfPageStatusFlow(viewModel)
+
+ viewModel.setHasScrolledToBottom(true, backgroundScope)
+ runCurrent()
+
+ assertThat(pageStatusList[pageStatusList.size-1].hasScrollToBottom()).isEqualTo(true)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnNextButtonClick_enrollNext() = runTest {
+ // Set latest status to FINGERPRINT_ENROLLABLE_OK
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 0)
+ setupSuwMaxFingerprintsEnrollable(application, resources, 1)
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ newIsSuwRequest(application)
+ )
+
+ val actions = listOfActionFlow(viewModel)
+
+ // Perform click on `next`
+ viewModel.onNextButtonClick(backgroundScope)
+ runCurrent()
+
+ assertThat(actions.size).isEqualTo(1)
+ assertThat(actions[0]).isEqualTo(CONTINUE_ENROLL)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnNextButtonClick_doneAndFinish() = runTest {
+ // Set latest status to FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX
+ setupFingerprintEnrolledFingerprints(fingerprintManager, TEST_USER_ID, 1)
+ setupSuwMaxFingerprintsEnrollable(application, resources, 1)
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ newIsSuwRequest(application)
+ )
+
+ val statusList = listOfPageStatusFlow(viewModel)
+ val actionList = listOfActionFlow(viewModel)
+
+ runCurrent()
+
+ assertThat(statusList.size).isEqualTo(1)
+ assertThat(statusList[0].enrollableStatus).isEqualTo(FINGERPRINT_ENROLLABLE_ERROR_REACH_MAX)
+
+ val actions = listOfActionFlow(viewModel)
+
+ // Perform click on `next`
+ viewModel.onNextButtonClick(backgroundScope)
+ runCurrent()
+
+ assertThat(actionList.size).isEqualTo(1)
+ assertThat(actionList[0]).isEqualTo(DONE_AND_FINISH)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testOnSkipOrCancelButtonClick() = runTest {
+ val viewModel = newFingerprintEnrollIntroViewModel(
+ newFingerprintRepository(fingerprintManager, TYPE_UDFPS_OPTICAL, 5),
+ newAllFalseRequest(application)
+ )
+
+ val actions = listOfActionFlow(viewModel)
+
+ viewModel.onSkipOrCancelButtonClick(backgroundScope)
+ runCurrent()
+
+ assertThat(actions.size).isEqualTo(1)
+ assertThat(actions[0]).isEqualTo(SKIP_OR_CANCEL)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun TestScope.listOfActionFlow(
+ viewModel: FingerprintEnrollIntroViewModel
+ ): List<FingerprintEnrollIntroAction> =
+ mutableListOf<FingerprintEnrollIntroAction>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.actionFlow.toList(it)
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun TestScope.listOfPageStatusFlow(
+ viewModel: FingerprintEnrollIntroViewModel
+ ): List<FingerprintEnrollIntroStatus> =
+ mutableListOf<FingerprintEnrollIntroStatus>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.pageStatusFlow.toList(it)
+ }
+ }
+
+ companion object {
+ private const val TEST_USER_ID = 33
+ }
+}