[BiometricsV2] Refactor AutoCredentialViewModel
Refactor AutoCredentialViewModelTest and FingerprintEnrollmentViewModel
to kotlin and change LiveData to Flow
Bug: 286197659
Test: atest -m CredentialModelTest
Test: atest -m AutoCredentialViewModelTest
Test: atest -m FingerprintEnrollmentViewModelTest
Test: atest -m FingerprintEnrollmentActivityTest
Test: atest -m biometrics-enrollment-test
Change-Id: I84bab0b46e023303c0046a6ae6886ab1cf9458b8
diff --git a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java
index b83614c..2cf607a 100644
--- a/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java
+++ b/src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java
@@ -28,6 +28,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
+import com.android.settings.biometrics2.ui.model.CredentialModel;
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
@@ -54,8 +55,8 @@
new CreationExtras.Key<ChallengeGenerator>() {};
public static final CreationExtras.Key<EnrollmentRequest> ENROLLMENT_REQUEST_KEY =
new CreationExtras.Key<EnrollmentRequest>() {};
- public static final CreationExtras.Key<Integer> USER_ID_KEY =
- new CreationExtras.Key<Integer>() {};
+ public static final CreationExtras.Key<CredentialModel> CREDENTIAL_MODEL_KEY =
+ new CreationExtras.Key<CredentialModel>() {};
@NonNull
@Override
@@ -76,9 +77,10 @@
final LockPatternUtils lockPatternUtils =
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR_KEY);
- if (challengeGenerator != null) {
+ final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
+ if (challengeGenerator != null && credentialModel != null) {
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
- challengeGenerator);
+ challengeGenerator, credentialModel);
}
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
@@ -93,10 +95,10 @@
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
final FingerprintRepository repository = provider.getFingerprintRepository(application);
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
- final Integer userId = extras.get(USER_ID_KEY);
- if (repository != null && request != null && userId != null) {
+ final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
+ if (repository != null && request != null && credentialModel != null) {
return (T) new FingerprintEnrollIntroViewModel(application, repository, request,
- userId);
+ credentialModel.getUserId());
}
} else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) {
final FingerprintRepository repository = provider.getFingerprintRepository(application);
@@ -105,27 +107,27 @@
return (T) new FingerprintEnrollmentViewModel(application, repository, request);
}
} else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
- final Integer userId = extras.get(USER_ID_KEY);
- if (userId != null) {
+ final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
+ if (credentialModel != null) {
return (T) new FingerprintEnrollProgressViewModel(application,
- new FingerprintUpdater(application), userId);
+ new FingerprintUpdater(application), credentialModel.getUserId());
}
} else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) {
- final Integer userId = extras.get(USER_ID_KEY);
+ final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
application);
- if (fingerprint != null && userId != null) {
- return (T) new FingerprintEnrollEnrollingViewModel(application, userId,
- fingerprint);
+ if (fingerprint != null && credentialModel != null) {
+ return (T) new FingerprintEnrollEnrollingViewModel(application,
+ credentialModel.getUserId(), fingerprint);
}
} else if (modelClass.isAssignableFrom(FingerprintEnrollFinishViewModel.class)) {
- final Integer userId = extras.get(USER_ID_KEY);
+ final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
application);
- if (fingerprint != null && userId != null && request != null) {
- return (T) new FingerprintEnrollFinishViewModel(application, userId, request,
- fingerprint);
+ if (fingerprint != null && credentialModel != null && request != null) {
+ return (T) new FingerprintEnrollFinishViewModel(application,
+ credentialModel.getUserId(), request, fingerprint);
}
} else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) {
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
diff --git a/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt b/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt
index 7999ab8..5350733 100644
--- a/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt
+++ b/src/com/android/settings/biometrics2/ui/model/CredentialModel.kt
@@ -80,20 +80,6 @@
val isValidToken: Boolean
get() = token != null
- val bundle: Bundle
- /**
- * Get a bundle which can be used to recreate CredentialModel
- */
- get() {
- val bundle = Bundle()
- bundle.putInt(EXTRA_USER_ID, userId)
- bundle.putLong(EXTRA_KEY_CHALLENGE, challenge)
- bundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, token)
- bundle.putLong(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
- return bundle
- }
-
-
/** Returns a string representation of the object */
override fun toString(): String {
val gkPwHandleLen = "$gkPwHandle".length
diff --git a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
index fa8a3fa..562b7dd 100644
--- a/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
+++ b/src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt
@@ -44,16 +44,13 @@
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR_KEY
+import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CREDENTIAL_MODEL_KEY
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.ENROLLMENT_REQUEST_KEY
-import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.USER_ID_KEY
import com.android.settings.biometrics2.ui.model.CredentialModel
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel
-import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK
-import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK
-import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE
-import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator
+import com.android.settings.biometrics2.ui.viewmodel.CredentialAction
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE
@@ -170,7 +167,6 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- autoCredentialViewModel.setCredentialModel(savedInstanceState, intent)
// Theme
setTheme(viewModel.request.theme)
@@ -219,15 +215,24 @@
}
}
- // observe LiveData
- viewModel.setResultLiveData.observe(this) {
- result: ActivityResult -> onSetActivityResult(result)
- }
- autoCredentialViewModel.generateChallengeFailedLiveData.observe(this) {
- _: Boolean -> onGenerateChallengeFailed()
- }
+ collectFlows()
+ }
+
+ private fun collectFlows() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.setResultFlow.collect {
+ Log.d(TAG, "setResultLiveData($it)")
+ onSetActivityResult(it)
+ }
+ }
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ autoCredentialViewModel.generateChallengeFailedFlow.collect {
+ Log.d(TAG, "generateChallengeFailedFlow($it)")
+ onSetActivityResult(ActivityResult(RESULT_CANCELED, null))
+ }
+ }
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.newDialogFlow.collect {
Log.d(TAG, "newErrorDialogFlow($it)")
FingerprintEnrollErrorDialog.newInstance(it).show(
@@ -236,8 +241,6 @@
)
}
}
- }
- lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorDialogViewModel.setResultFlow.collect {
Log.d(TAG, "errorDialogSetResultFlow($it)")
@@ -408,10 +411,6 @@
}
}
- private fun onGenerateChallengeFailed() {
- onSetActivityResult(ActivityResult(RESULT_CANCELED, null))
- }
-
private fun onSetActivityResult(result: ActivityResult) {
val challengeExtras: Bundle? = autoCredentialViewModel.createGeneratingChallengeExtras()
val overrideResult: ActivityResult = viewModel.getOverrideActivityResult(
@@ -428,8 +427,8 @@
}
private fun checkCredential() {
- when (autoCredentialViewModel.checkCredential()) {
- CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK -> {
+ when (autoCredentialViewModel.checkCredential(lifecycleScope)) {
+ CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK -> {
val intent: Intent = autoCredentialViewModel.createChooseLockIntent(
this,
viewModel.request.isSuw,
@@ -442,7 +441,7 @@
return
}
- CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK -> {
+ CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK -> {
val launched: Boolean = autoCredentialViewModel.createConfirmLockLauncher(
this,
LAUNCH_CONFIRM_LOCK_ACTIVITY,
@@ -459,21 +458,24 @@
return
}
- CREDENTIAL_VALID,
- CREDENTIAL_IS_GENERATING_CHALLENGE -> {}
+ CredentialAction.CREDENTIAL_VALID,
+ CredentialAction.IS_GENERATING_CHALLENGE -> {}
}
}
- private fun onChooseOrConfirmLockResult(isChooseLock: Boolean, activityResult: ActivityResult) {
+ private fun onChooseOrConfirmLockResult(
+ isChooseLock: Boolean,
+ activityResult: ActivityResult
+ ) {
if (!viewModel.isWaitingActivityResult.compareAndSet(true, false)) {
Log.w(TAG, "isChooseLock:$isChooseLock, fail to unset waiting flag")
}
- if (autoCredentialViewModel.checkNewCredentialFromActivityResult(
- isChooseLock, activityResult
+ if (!autoCredentialViewModel.generateChallengeAsCredentialActivityResult(
+ isChooseLock,
+ activityResult,
+ lifecycleScope
)
) {
- overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out)
- } else {
onSetActivityResult(activityResult)
}
}
@@ -573,7 +575,11 @@
override fun onPause() {
super.onPause()
- viewModel.checkFinishActivityDuringOnPause(isFinishing, isChangingConfigurations)
+ viewModel.checkFinishActivityDuringOnPause(
+ isFinishing,
+ isChangingConfigurations,
+ lifecycleScope
+ )
}
override fun onDestroy() {
@@ -596,17 +602,14 @@
}
override val defaultViewModelCreationExtras: CreationExtras
- get() {
- val fingerprintRepository = featureFactory.biometricsRepositoryProvider
- .getFingerprintRepository(application)!!
- val credentialModel = CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
-
- return MutableCreationExtras(super.defaultViewModelCreationExtras).also {
- it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(fingerprintRepository)
- it[ENROLLMENT_REQUEST_KEY] =
- EnrollmentRequest(intent, applicationContext, this is SetupActivity)
- it[USER_ID_KEY] = credentialModel.userId
- }
+ get() = MutableCreationExtras(super.defaultViewModelCreationExtras).also {
+ it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(
+ featureFactory.biometricsRepositoryProvider.getFingerprintRepository(application)!!
+ )
+ it[ENROLLMENT_REQUEST_KEY] =
+ EnrollmentRequest(intent, applicationContext, this is SetupActivity)
+ it[CREDENTIAL_MODEL_KEY] =
+ CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
}
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
@@ -630,11 +633,6 @@
super.onConfigurationChanged(newConfig)
}
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- autoCredentialViewModel.onSaveInstanceState(outState)
- }
-
companion object {
private const val DEBUG = false
private const val TAG = "FingerprintEnrollmentActivity"
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
deleted file mode 100644
index 7e48f82..0000000
--- a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.java
+++ /dev/null
@@ -1,393 +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 android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
-import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE;
-import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
-
-import android.annotation.IntDef;
-import android.app.Activity;
-import android.app.Application;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.activity.result.ActivityResult;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.settings.biometrics.BiometricUtils;
-import com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException;
-import com.android.settings.biometrics2.data.repository.FingerprintRepository;
-import com.android.settings.biometrics2.ui.model.CredentialModel;
-import com.android.settings.password.ChooseLockGeneric;
-import com.android.settings.password.ChooseLockPattern;
-import com.android.settings.password.ChooseLockSettingsHelper;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like
- * start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing.
- */
-public class AutoCredentialViewModel extends AndroidViewModel {
-
- private static final String TAG = "AutoCredentialViewModel";
-
- @VisibleForTesting
- static final String KEY_CREDENTIAL_MODEL = "credential_model";
-
- @VisibleForTesting
- static final String KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL =
- "is_generating_challenge_during_checking_credential";
-
- private static final boolean DEBUG = false;
-
- /**
- * Valid credential, activity does nothing.
- */
- public static final int CREDENTIAL_VALID = 0;
-
- /**
- * This credential looks good, but still need to run generateChallenge().
- */
- public static final int CREDENTIAL_IS_GENERATING_CHALLENGE = 1;
-
- /**
- * Need activity to run choose lock
- */
- public static final int CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK = 2;
-
- /**
- * Need activity to run confirm lock
- */
- public static final int CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK = 3;
-
- @IntDef(prefix = { "CREDENTIAL_" }, value = {
- CREDENTIAL_VALID,
- CREDENTIAL_IS_GENERATING_CHALLENGE,
- CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK,
- CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CredentialAction {}
-
- /**
- * Generic callback for FingerprintManager#generateChallenge or FaceManager#generateChallenge
- */
- public interface GenerateChallengeCallback {
- /**
- * Generic generateChallenge method for FingerprintManager or FaceManager
- */
- void onChallengeGenerated(int sensorId, int userId, long challenge);
- }
-
- /**
- * A generic interface class for calling different generateChallenge from FingerprintManager or
- * FaceManager
- */
- public interface ChallengeGenerator {
- /**
- * Get callback that will be called later after challenge generated
- */
- @Nullable
- GenerateChallengeCallback getCallback();
-
- /**
- * Set callback that will be called later after challenge generated
- */
- void setCallback(@Nullable GenerateChallengeCallback callback);
-
- /**
- * Method for generating challenge from FingerprintManager or FaceManager
- */
- void generateChallenge(int userId);
- }
-
- /**
- * Used to generate challenge through FingerprintRepository
- */
- public static class FingerprintChallengeGenerator implements ChallengeGenerator {
-
- private static final String TAG = "FingerprintChallengeGenerator";
-
- @NonNull
- private final FingerprintRepository mFingerprintRepository;
-
- @Nullable
- private GenerateChallengeCallback mCallback = null;
-
- public FingerprintChallengeGenerator(@NonNull FingerprintRepository fingerprintRepository) {
- mFingerprintRepository = fingerprintRepository;
- }
-
- @Nullable
- @Override
- public GenerateChallengeCallback getCallback() {
- return mCallback;
- }
-
- @Override
- public void setCallback(@Nullable GenerateChallengeCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public void generateChallenge(int userId) {
- final GenerateChallengeCallback callback = mCallback;
- if (callback == null) {
- Log.e(TAG, "generateChallenge, null callback");
- return;
- }
- mFingerprintRepository.generateChallenge(userId, callback::onChallengeGenerated);
- }
- }
-
- @NonNull private final LockPatternUtils mLockPatternUtils;
- @NonNull private final ChallengeGenerator mChallengeGenerator;
- private CredentialModel mCredentialModel = null;
- @NonNull private final MutableLiveData<Boolean> mGenerateChallengeFailedLiveData =
- new MutableLiveData<>();
-
- // flag if token is generating through checkCredential()'s generateChallenge()
- private boolean mIsGeneratingChallengeDuringCheckingCredential;
-
- public AutoCredentialViewModel(
- @NonNull Application application,
- @NonNull LockPatternUtils lockPatternUtils,
- @NonNull ChallengeGenerator challengeGenerator) {
- super(application);
- mLockPatternUtils = lockPatternUtils;
- mChallengeGenerator = challengeGenerator;
- }
-
- /**
- * Set CredentialModel, the source is coming from savedInstanceState or activity intent
- */
- public void setCredentialModel(@Nullable Bundle savedInstanceState, @NonNull Intent intent) {
- final Bundle bundle;
- if (savedInstanceState != null) {
- bundle = savedInstanceState.getBundle(KEY_CREDENTIAL_MODEL);
- mIsGeneratingChallengeDuringCheckingCredential = savedInstanceState.getBoolean(
- KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL);
- } else {
- bundle = intent.getExtras();
- }
- mCredentialModel = new CredentialModel(bundle, SystemClock.elapsedRealtimeClock());
-
- if (DEBUG) {
- Log.d(TAG, "setCredentialModel " + mCredentialModel + ", savedInstanceState exist:"
- + (savedInstanceState != null));
- }
- }
-
- /**
- * Handle onSaveInstanceState from activity
- */
- public void onSaveInstanceState(@NonNull Bundle outState) {
- outState.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL,
- mIsGeneratingChallengeDuringCheckingCredential);
- outState.putBundle(KEY_CREDENTIAL_MODEL, mCredentialModel.getBundle());
- }
-
- @NonNull
- public LiveData<Boolean> getGenerateChallengeFailedLiveData() {
- return mGenerateChallengeFailedLiveData;
- }
-
- /**
- * Get bundle which passing back to FingerprintSettings for late generateChallenge()
- */
- @Nullable
- public Bundle createGeneratingChallengeExtras() {
- if (!mIsGeneratingChallengeDuringCheckingCredential
- || !mCredentialModel.isValidToken()
- || !mCredentialModel.isValidChallenge()) {
- return null;
- }
-
- Bundle bundle = new Bundle();
- bundle.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
- mCredentialModel.getToken());
- bundle.putLong(EXTRA_KEY_CHALLENGE, mCredentialModel.getChallenge());
- return bundle;
- }
-
- /**
- * Check credential status for biometric enrollment.
- */
- @CredentialAction
- public int checkCredential() {
- if (isValidCredential()) {
- return CREDENTIAL_VALID;
- }
- if (isUnspecifiedPassword()) {
- return CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
- } else if (mCredentialModel.isValidGkPwHandle()) {
- final long gkPwHandle = mCredentialModel.getGkPwHandle();
- mCredentialModel.clearGkPwHandle();
- // GkPwHandle is got through caller activity, we shall not revoke it after
- // generateChallenge(). Let caller activity to make decision.
- generateChallenge(gkPwHandle, false /* revokeGkPwHandle */);
- mIsGeneratingChallengeDuringCheckingCredential = true;
- return CREDENTIAL_IS_GENERATING_CHALLENGE;
- } else {
- return CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
- }
- }
-
- private void generateChallenge(long gkPwHandle, boolean revokeGkPwHandle) {
- mChallengeGenerator.setCallback((sensorId, userId, challenge) -> {
- try {
- final byte[] newToken = requestGatekeeperHat(gkPwHandle, challenge, userId);
- mCredentialModel.setChallenge(challenge);
- mCredentialModel.setToken(newToken);
- } catch (IllegalStateException e) {
- Log.e(TAG, "generateChallenge, IllegalStateException", e);
- mGenerateChallengeFailedLiveData.postValue(true);
- return;
- }
-
- if (revokeGkPwHandle) {
- mLockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle);
- }
-
- if (DEBUG) {
- Log.d(TAG, "generateChallenge(), model:" + mCredentialModel
- + ", revokeGkPwHandle:" + revokeGkPwHandle);
- }
-
- // Check credential again
- if (!isValidCredential()) {
- Log.w(TAG, "generateChallenge, invalid Credential");
- mGenerateChallengeFailedLiveData.postValue(true);
- }
- });
- mChallengeGenerator.generateChallenge(getUserId());
- }
-
- private boolean isValidCredential() {
- return !isUnspecifiedPassword() && mCredentialModel.isValidToken();
- }
-
- private boolean isUnspecifiedPassword() {
- return mLockPatternUtils.getActivePasswordQuality(getUserId())
- == PASSWORD_QUALITY_UNSPECIFIED;
- }
-
- /**
- * Handle activity result from ChooseLockGeneric, ConfirmLockPassword, or ConfirmLockPattern
- * @param isChooseLock true if result is coming from ChooseLockGeneric. False if result is
- * coming from ConfirmLockPassword or ConfirmLockPattern
- * @param result activity result
- * @return if it is a valid result
- */
- public boolean checkNewCredentialFromActivityResult(boolean isChooseLock,
- @NonNull ActivityResult result) {
- if ((isChooseLock && result.getResultCode() == ChooseLockPattern.RESULT_FINISHED)
- || (!isChooseLock && result.getResultCode() == Activity.RESULT_OK)) {
- final Intent data = result.getData();
- if (data != null) {
- final long gkPwHandle = result.getData().getLongExtra(
- EXTRA_KEY_GK_PW_HANDLE, INVALID_GK_PW_HANDLE);
- // Revoke self requested GkPwHandle because it shall only used once inside this
- // activity lifecycle.
- generateChallenge(gkPwHandle, true /* revokeGkPwHandle */);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Get userId for this credential
- */
- public int getUserId() {
- return mCredentialModel.getUserId();
- }
-
- /**
- * Get userId for this credential
- */
- @Nullable
- public byte[] getToken() {
- return mCredentialModel.getToken();
- }
-
- @Nullable
- private byte[] requestGatekeeperHat(long gkPwHandle, long challenge, int userId)
- throws IllegalStateException {
- final VerifyCredentialResponse response = mLockPatternUtils
- .verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId);
- if (!response.isMatched()) {
- throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT");
- }
- return response.getGatekeeperHAT();
- }
-
- /**
- * Create Intent for choosing lock
- */
- @NonNull
- public Intent createChooseLockIntent(@NonNull Context context, boolean isSuw,
- @NonNull Bundle suwExtras) {
- final Intent intent = BiometricUtils.getChooseLockIntent(context, isSuw,
- suwExtras);
- intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS,
- true);
- intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
- intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true);
-
- if (mCredentialModel.isValidUserId()) {
- intent.putExtra(Intent.EXTRA_USER_ID, mCredentialModel.getUserId());
- }
- return intent;
- }
-
- /**
- * Create ConfirmLockLauncher
- */
- @NonNull
- public ChooseLockSettingsHelper createConfirmLockLauncher(@NonNull Activity activity,
- int requestCode, @NonNull String title) {
- final ChooseLockSettingsHelper.Builder builder =
- new ChooseLockSettingsHelper.Builder(activity);
- builder.setRequestCode(requestCode)
- .setTitle(title)
- .setRequestGatekeeperPasswordHandle(true)
- .setForegroundOnly(true)
- .setReturnCredentials(true);
-
- if (mCredentialModel.isValidUserId()) {
- builder.setUserId(mCredentialModel.getUserId());
- }
- return builder.build();
- }
-
-}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.kt b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.kt
new file mode 100644
index 0000000..3fd4d51
--- /dev/null
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.kt
@@ -0,0 +1,300 @@
+/*
+ * 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.Activity
+import android.app.Application
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.result.ActivityResult
+import androidx.lifecycle.AndroidViewModel
+import com.android.internal.widget.LockPatternUtils
+import com.android.settings.biometrics.BiometricEnrollBase
+import com.android.settings.biometrics.BiometricUtils
+import com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException
+import com.android.settings.biometrics2.data.repository.FingerprintRepository
+import com.android.settings.biometrics2.ui.model.CredentialModel
+import com.android.settings.password.ChooseLockGeneric
+import com.android.settings.password.ChooseLockPattern
+import com.android.settings.password.ChooseLockSettingsHelper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+
+/**
+ * AutoCredentialViewModel which uses CredentialModel to determine next actions for activity, like
+ * start ChooseLockActivity, start ConfirmLockActivity, GenerateCredential, or do nothing.
+ */
+class AutoCredentialViewModel(
+ application: Application,
+ private val lockPatternUtils: LockPatternUtils,
+ private val challengeGenerator: ChallengeGenerator,
+ private val credentialModel: CredentialModel
+) : AndroidViewModel(application) {
+
+ /**
+ * Generic callback for FingerprintManager#generateChallenge or FaceManager#generateChallenge
+ */
+ interface GenerateChallengeCallback {
+ /** Generic generateChallenge method for FingerprintManager or FaceManager */
+ fun onChallengeGenerated(sensorId: Int, userId: Int, challenge: Long)
+ }
+
+ /**
+ * A generic interface class for calling different generateChallenge from FingerprintManager or
+ * FaceManager
+ */
+ interface ChallengeGenerator {
+
+ /** Get callback that will be called later after challenge generated */
+ fun getCallback(): GenerateChallengeCallback?
+
+ /** Set callback that will be called later after challenge generated */
+ fun setCallback(callback: GenerateChallengeCallback?)
+
+ /** Method for generating challenge from FingerprintManager or FaceManager */
+ fun generateChallenge(userId: Int)
+ }
+
+ /** Used to generate challenge through FingerprintRepository */
+ class FingerprintChallengeGenerator(
+ private val fingerprintRepository: FingerprintRepository
+ ) : ChallengeGenerator {
+
+ private var mCallback: GenerateChallengeCallback? = null
+
+ override fun getCallback(): GenerateChallengeCallback? {
+ return mCallback
+ }
+
+ override fun setCallback(callback: GenerateChallengeCallback?) {
+ mCallback = callback
+ }
+
+ override fun generateChallenge(userId: Int) {
+ val callback = mCallback
+ if (callback == null) {
+ Log.e(TAG, "generateChallenge, null callback")
+ return
+ }
+
+ fingerprintRepository.generateChallenge(userId) {
+ sensorId: Int, uid: Int, challenge: Long ->
+ callback.onChallengeGenerated(
+ sensorId,
+ uid,
+ challenge
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "FingerprintChallengeGenerator"
+ }
+ }
+
+ private val _generateChallengeFailedFlow = MutableSharedFlow<Boolean>()
+ val generateChallengeFailedFlow: SharedFlow<Boolean>
+ get() = _generateChallengeFailedFlow.asSharedFlow()
+
+
+ // flag if token is generating through checkCredential()'s generateChallenge()
+ private var isGeneratingChallengeDuringCheckingCredential = false
+
+ /** Get bundle which passing back to FingerprintSettings for late generateChallenge() */
+ fun createGeneratingChallengeExtras(): Bundle? {
+ if (!isGeneratingChallengeDuringCheckingCredential
+ || !credentialModel.isValidToken
+ || !credentialModel.isValidChallenge
+ ) {
+ return null
+ }
+ val bundle = Bundle()
+ bundle.putByteArray(
+ ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
+ credentialModel.token
+ )
+ bundle.putLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, credentialModel.challenge)
+ return bundle
+ }
+
+ /** Check credential status for biometric enrollment. */
+ fun checkCredential(scope: CoroutineScope): CredentialAction {
+ return if (isValidCredential) {
+ CredentialAction.CREDENTIAL_VALID
+ } else if (isUnspecifiedPassword) {
+ CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK
+ } else if (credentialModel.isValidGkPwHandle) {
+ val gkPwHandle = credentialModel.gkPwHandle
+ credentialModel.clearGkPwHandle()
+ // GkPwHandle is got through caller activity, we shall not revoke it after
+ // generateChallenge(). Let caller activity to make decision.
+ generateChallenge(gkPwHandle, false, scope)
+ isGeneratingChallengeDuringCheckingCredential = true
+ CredentialAction.IS_GENERATING_CHALLENGE
+ } else {
+ CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK
+ }
+ }
+
+ private fun generateChallenge(
+ gkPwHandle: Long,
+ revokeGkPwHandle: Boolean,
+ scope: CoroutineScope
+ ) {
+ challengeGenerator.setCallback(object : GenerateChallengeCallback {
+ override fun onChallengeGenerated(sensorId: Int, userId: Int, challenge: Long) {
+ var illegalStateExceptionCaught = false
+ try {
+ val newToken = requestGatekeeperHat(gkPwHandle, challenge, userId)
+ credentialModel.challenge = challenge
+ credentialModel.token = newToken
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "generateChallenge, IllegalStateException", e)
+ illegalStateExceptionCaught = true
+ } finally {
+ if (revokeGkPwHandle) {
+ lockPatternUtils.removeGatekeeperPasswordHandle(gkPwHandle)
+ }
+ Log.d(
+ TAG,
+ "generateChallenge(), model:$credentialModel"
+ + ", revokeGkPwHandle:$revokeGkPwHandle"
+ )
+ // Check credential again
+ if (!isValidCredential || illegalStateExceptionCaught) {
+ Log.w(TAG, "generateChallenge, invalid Credential or IllegalStateException")
+ scope.launch {
+ _generateChallengeFailedFlow.emit(true)
+ }
+ }
+ }
+ }
+ })
+ challengeGenerator.generateChallenge(userId)
+ }
+
+ private val isValidCredential: Boolean
+ get() = !isUnspecifiedPassword && credentialModel.isValidToken
+
+ private val isUnspecifiedPassword: Boolean
+ get() = lockPatternUtils.getActivePasswordQuality(userId) == PASSWORD_QUALITY_UNSPECIFIED
+
+ /**
+ * Handle activity result from ChooseLockGeneric, ConfirmLockPassword, or ConfirmLockPattern
+ * @param isChooseLock true if result is coming from ChooseLockGeneric. False if result is
+ * coming from ConfirmLockPassword or ConfirmLockPattern
+ * @param result activity result
+ * @return if it is a valid result and viewModel is generating challenge
+ */
+ fun generateChallengeAsCredentialActivityResult(
+ isChooseLock: Boolean,
+ result: ActivityResult,
+ scope: CoroutineScope
+ ): Boolean {
+ if ((isChooseLock && result.resultCode == ChooseLockPattern.RESULT_FINISHED) ||
+ (!isChooseLock && result.resultCode == Activity.RESULT_OK)) {
+ result.data?.let {
+ val gkPwHandle = it.getLongExtra(
+ ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
+ CredentialModel.INVALID_GK_PW_HANDLE
+ )
+ // Revoke self requested GkPwHandle because it shall only used once inside this
+ // activity lifecycle.
+ generateChallenge(gkPwHandle, true, scope)
+ return true
+ }
+ }
+ return false
+ }
+
+ val userId: Int
+ get() = credentialModel.userId
+
+ val token: ByteArray?
+ get() = credentialModel.token
+
+ @Throws(IllegalStateException::class)
+ private fun requestGatekeeperHat(gkPwHandle: Long, challenge: Long, userId: Int): ByteArray? {
+ val response = lockPatternUtils
+ .verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)
+ if (!response.isMatched) {
+ throw GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT")
+ }
+ return response.gatekeeperHAT
+ }
+
+ /** Create Intent for choosing lock */
+ fun createChooseLockIntent(
+ context: Context, isSuw: Boolean,
+ suwExtras: Bundle
+ ): Intent {
+ val intent = BiometricUtils.getChooseLockIntent(
+ context, isSuw,
+ suwExtras
+ )
+ intent.putExtra(
+ ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS,
+ true
+ )
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true)
+ if (credentialModel.isValidUserId) {
+ intent.putExtra(Intent.EXTRA_USER_ID, credentialModel.userId)
+ }
+ return intent
+ }
+
+ /** Create ConfirmLockLauncher */
+ fun createConfirmLockLauncher(
+ activity: Activity,
+ requestCode: Int, title: String
+ ): ChooseLockSettingsHelper {
+ val builder = ChooseLockSettingsHelper.Builder(activity)
+ builder.setRequestCode(requestCode)
+ .setTitle(title)
+ .setRequestGatekeeperPasswordHandle(true)
+ .setForegroundOnly(true)
+ .setReturnCredentials(true)
+ if (credentialModel.isValidUserId) {
+ builder.setUserId(credentialModel.userId)
+ }
+ return builder.build()
+ }
+
+ companion object {
+ private const val TAG = "AutoCredentialViewModel"
+ }
+}
+
+enum class CredentialAction {
+
+ CREDENTIAL_VALID,
+
+ /** Valid credential, activity does nothing. */
+ IS_GENERATING_CHALLENGE,
+
+ /** This credential looks good, but still need to run generateChallenge(). */
+ FAIL_NEED_TO_CHOOSE_LOCK,
+
+ /** Need activity to run confirm lock */
+ FAIL_NEED_TO_CONFIRM_LOCK
+}
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.kt b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.kt
index 33e1bb6..37b0052 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.kt
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModel.kt
@@ -23,8 +23,6 @@
import android.util.Log
import androidx.activity.result.ActivityResult
import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
@@ -32,6 +30,11 @@
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
/**
* Fingerprint enrollment view model implementation
@@ -44,9 +47,9 @@
val isWaitingActivityResult: AtomicBoolean = atomic(false)
- private val _setResultLiveData = MutableLiveData<ActivityResult>()
- val setResultLiveData: LiveData<ActivityResult>
- get() = _setResultLiveData
+ private val _setResultFlow = MutableSharedFlow<ActivityResult>()
+ val setResultFlow: SharedFlow<ActivityResult>
+ get() = _setResultFlow.asSharedFlow()
var isNewFingerprintAdded = false
set(value) {
@@ -94,16 +97,17 @@
*/
fun checkFinishActivityDuringOnPause(
isActivityFinishing: Boolean,
- isChangingConfigurations: Boolean
+ isChangingConfigurations: Boolean,
+ scope: CoroutineScope
) {
if (isChangingConfigurations || isActivityFinishing || request.isSuw
|| isWaitingActivityResult.value
) {
return
}
- _setResultLiveData.postValue(
- ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)
- )
+ scope.launch {
+ _setResultFlow.emit(ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null))
+ }
}
/**
@@ -133,23 +137,23 @@
* Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
*/
fun updateFingerprintSuggestionEnableState(userId: Int) {
- val enrolled = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
// Only show "Add another fingerprint" if the user already enrolled one.
// "Add fingerprint" will be shown in the main flow if the user hasn't enrolled any
// fingerprints. If the user already added more than one fingerprint, they already know
// to add multiple fingerprints so we don't show the suggestion.
+ val state = if (fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId) == 1)
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ else
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
getApplication<Application>().packageManager.setComponentEnabledSetting(
ComponentName(
getApplication(),
FINGERPRINT_SUGGESTION_ACTIVITY
),
- if (enrolled == 1)
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- else
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ state,
PackageManager.DONT_KILL_APP
)
- Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state = ${enrolled == 1}")
+ Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state: $state")
}
companion object {
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.kt b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.kt
index d718db6..ac62232 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.kt
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/model/CredentialModelTest.kt
@@ -38,22 +38,6 @@
Truth.assertThat(credentialModel.userId).isEqualTo(UserHandle.myUserId())
}
- @Test
- fun testSameValueFromBundle() {
- val bundle = newCredentialModelIntentExtras(1234, 6677L, byteArrayOf(33, 44, 55), 987654321)
- val model1 = CredentialModel(bundle, clock)
- val model2 = CredentialModel(model1.bundle, clock)
- verifySameCredentialModels(model1, model2)
- }
-
- @Test
- fun testSameValueFromBundle_nullToken() {
- val bundle = newCredentialModelIntentExtras(22, 33L, null, 21L)
- val model1 = CredentialModel(bundle, clock)
- val model2 = CredentialModel(model1.bundle, clock)
- verifySameCredentialModels(model1, model2)
- }
-
companion object {
@JvmStatic
fun newCredentialModelIntentExtras(
@@ -148,36 +132,5 @@
}
}
}
-
- fun verifySameCredentialModels(
- model1: CredentialModel,
- model2: CredentialModel
- ) {
- Truth.assertThat(model1.userId).isEqualTo(model2.userId)
- Truth.assertThat(model1.challenge).isEqualTo(model2.challenge)
- Truth.assertThat(model1.gkPwHandle).isEqualTo(model2.gkPwHandle)
- val token1 = model1.token
- val token2 = model2.token
- if (token1 == null) {
- Truth.assertThat(token2).isNull()
- } else {
- Truth.assertThat(token2).isNotNull()
- Truth.assertThat(token1.size).isEqualTo(token2!!.size)
- for (i in token1.indices) {
- Truth.assertThat(token1[i]).isEqualTo(
- token2[i]
- )
- }
- }
- val bundle1 = model1.bundle
- val bundle2 = model2.bundle
- val keySet1 = bundle1.keySet()
- Truth.assertThat(keySet1 == bundle2.keySet()).isTrue()
- checkBundleIntValue(bundle1, bundle2, Intent.EXTRA_USER_ID)
- checkBundleIntValue(bundle1, bundle2, BiometricEnrollBase.EXTRA_KEY_SENSOR_ID)
- checkBundleLongValue(bundle1, bundle2, BiometricEnrollBase.EXTRA_KEY_CHALLENGE)
- checkBundleByteArrayValue(bundle1, bundle2, BiometricEnrollBase.EXTRA_KEY_CHALLENGE)
- checkBundleLongValue(bundle1, bundle2, ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE)
- }
}
}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
deleted file mode 100644
index 05a7239..0000000
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.java
+++ /dev/null
@@ -1,596 +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.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_CHALLENGE;
-import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_CHALLENGE;
-import static com.android.settings.biometrics2.ui.model.CredentialModel.INVALID_GK_PW_HANDLE;
-import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newCredentialModelIntentExtras;
-import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newGkPwHandleCredentialIntentExtras;
-import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newOnlySensorValidCredentialIntentExtras;
-import static com.android.settings.biometrics2.ui.model.CredentialModelTest.newValidTokenCredentialIntentExtras;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CredentialAction;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.GenerateChallengeCallback;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.KEY_CREDENTIAL_MODEL;
-import static com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL;
-import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN;
-import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import androidx.activity.result.ActivityResult;
-import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.settings.password.ChooseLockPattern;
-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;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@RunWith(AndroidJUnit4.class)
-public class AutoCredentialViewModelTest {
-
- @Rule public final MockitoRule mockito = MockitoJUnit.rule();
- @Rule public final InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();
-
- @Mock private LockPatternUtils mLockPatternUtils;
- private TestChallengeGenerator mChallengeGenerator = null;
- private AutoCredentialViewModel mViewModel;
-
- @Before
- public void setUp() {
- mChallengeGenerator = new TestChallengeGenerator();
- mViewModel = new AutoCredentialViewModel(
- ApplicationProvider.getApplicationContext(),
- mLockPatternUtils,
- mChallengeGenerator);
- }
-
- private void setupGenerateChallenge(int userId, int newSensorId, long newChallenge) {
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
- mChallengeGenerator.mUserId = userId;
- mChallengeGenerator.mSensorId = newSensorId;
- mChallengeGenerator.mChallenge = newChallenge;
- }
-
- @Test
- public void testSetCredentialModel_sameResultFromSavedInstanceOrIntent() {
- final Bundle extras = newCredentialModelIntentExtras(12, 33, new byte[] { 2, 3 }, 3L);
-
- AutoCredentialViewModel viewModel2 = new AutoCredentialViewModel(
- ApplicationProvider.getApplicationContext(),
- mLockPatternUtils,
- mChallengeGenerator);
-
- mViewModel.setCredentialModel(null, new Intent().putExtras(extras));
- final Bundle savedInstance = new Bundle();
- mViewModel.onSaveInstanceState(savedInstance);
- viewModel2.setCredentialModel(savedInstance, new Intent());
-
- assertThat(mViewModel.getUserId()).isEqualTo(viewModel2.getUserId());
- final byte[] token1 = mViewModel.getToken();
- final byte[] token2 = viewModel2.getToken();
- assertThat(token1).isNotNull();
- assertThat(token2).isNotNull();
- assertThat(token1.length).isEqualTo(token2.length);
- for (int i = 0; i < token2.length; ++i) {
- assertThat(token1[i]).isEqualTo(token2[i]);
- }
- }
-
- @Test
- public void testSetCredentialModel_sameResultFromSavedInstanceOrIntent_invalidValues() {
- final Bundle extras = newCredentialModelIntentExtras(UserHandle.USER_NULL,
- INVALID_CHALLENGE, null, INVALID_GK_PW_HANDLE);
-
- AutoCredentialViewModel viewModel2 = new AutoCredentialViewModel(
- ApplicationProvider.getApplicationContext(),
- mLockPatternUtils,
- mChallengeGenerator);
-
- mViewModel.setCredentialModel(null, new Intent().putExtras(extras));
- final Bundle savedInstance = new Bundle();
- mViewModel.onSaveInstanceState(savedInstance);
- viewModel2.setCredentialModel(savedInstance, new Intent());
-
- assertThat(mViewModel.getUserId()).isEqualTo(UserHandle.USER_NULL);
- assertThat(viewModel2.getUserId()).isEqualTo(UserHandle.USER_NULL);
- assertThat(mViewModel.getToken()).isNull();
- assertThat(viewModel2.getToken()).isNull();
- }
-
- @Test
- public void testCheckCredential_validCredentialCase() {
- final int userId = 99;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newValidTokenCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- // Check viewModel behavior
- assertThat(action).isEqualTo(CREDENTIAL_VALID);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isFalse();
- }
-
- @Test
- public void testCheckCredential_needToChooseLock() {
- final int userId = 100;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_UNSPECIFIED);
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- // Check viewModel behavior
- assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isFalse();
- }
-
- @Test
- public void testCheckCredential_needToConfirmLockForSomething() {
- final int userId = 101;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- // Check viewModel behavior
- assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isFalse();
- }
-
- @Test
- public void testCheckCredential_needToConfirmLockForNumeric() {
- final int userId = 102;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_NUMERIC);
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- // Check viewModel behavior
- assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isFalse();
- }
-
- @Test
- public void testCheckCredential_needToConfirmLockForAlphabetic() {
- final int userId = 103;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_ALPHABETIC);
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- // Check viewModel behavior
- assertThat(action).isEqualTo(CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isFalse();
- }
-
- @Test
- public void testCheckCredential_generateChallenge() {
- final int userId = 104;
- final long gkPwHandle = 1111L;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
-
- final int newSensorId = 10;
- final long newChallenge = 20L;
- setupGenerateChallenge(userId, newSensorId, newChallenge);
- when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
- .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
-
- final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
- doAnswer(invocation -> {
- hasCalledRemoveGkPwHandle.set(true);
- return null;
- }).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- // Check viewModel behavior
- assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
-
- // Check data inside CredentialModel
- assertThat(mViewModel.getToken()).isNotNull();
- assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
- assertThat(hasCalledRemoveGkPwHandle.get()).isFalse();
-
- // Check createGeneratingChallengeExtras()
- final Bundle generatingChallengeExtras = mViewModel.createGeneratingChallengeExtras();
- assertThat(generatingChallengeExtras).isNotNull();
- assertThat(generatingChallengeExtras.getLong(EXTRA_KEY_CHALLENGE)).isEqualTo(newChallenge);
- final byte[] tokens = generatingChallengeExtras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
- assertThat(tokens).isNotNull();
- assertThat(tokens.length).isEqualTo(1);
- assertThat(tokens[0]).isEqualTo(1);
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isTrue();
- }
-
- @Test
- public void testCheckCredential_generateChallengeFail() {
- final int userId = 104;
- final long gkPwHandle = 1111L;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
-
- final int newSensorId = 10;
- final long newChallenge = 20L;
- setupGenerateChallenge(userId, newSensorId, newChallenge);
- when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
- .thenReturn(newBadCredential(0));
-
- // Run credential check
- @CredentialAction final int action = mViewModel.checkCredential();
-
- assertThat(action).isEqualTo(CREDENTIAL_IS_GENERATING_CHALLENGE);
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isTrue();
- assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
-
- // Check onSaveInstanceState()
- final Bundle actualBundle = new Bundle();
- mViewModel.onSaveInstanceState(actualBundle);
- assertThat(actualBundle.getBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL))
- .isTrue();
- }
-
- @Test
- public void testGetUserId_fromIntent() {
- final int userId = 106;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
-
- // Get userId
- assertThat(mViewModel.getUserId()).isEqualTo(userId);
- }
-
- @Test
- public void testGetUserId_fromSavedInstance() {
- final int userId = 106;
- final Bundle savedInstance = new Bundle();
- savedInstance.putBundle(KEY_CREDENTIAL_MODEL,
- newOnlySensorValidCredentialIntentExtras(userId));
- mViewModel.setCredentialModel(savedInstance, new Intent());
-
- // Get userId
- assertThat(mViewModel.getUserId()).isEqualTo(userId);
- }
-
- @Test
- public void testCreateGeneratingChallengeExtras_generateChallenge() {
- final Bundle credentialExtras = newValidTokenCredentialIntentExtras(200);
- final Bundle savedInstance = new Bundle();
- savedInstance.putBundle(KEY_CREDENTIAL_MODEL, credentialExtras);
- savedInstance.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, true);
- mViewModel.setCredentialModel(savedInstance, new Intent());
-
- // Check createGeneratingChallengeExtras()
- final Bundle actualExtras = mViewModel.createGeneratingChallengeExtras();
- assertThat(actualExtras).isNotNull();
- assertThat(actualExtras.getLong(EXTRA_KEY_CHALLENGE))
- .isEqualTo(credentialExtras.getLong(EXTRA_KEY_CHALLENGE));
- final byte[] actualToken = actualExtras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
- final byte[] expectedToken = credentialExtras.getByteArray(EXTRA_KEY_CHALLENGE_TOKEN);
- assertThat(actualToken).isNotNull();
- assertThat(expectedToken).isNotNull();
- assertThat(actualToken.length).isEqualTo(expectedToken.length);
- for (int i = 0; i < actualToken.length; ++i) {
- assertWithMessage("tokens[" + i + "] not match").that(actualToken[i])
- .isEqualTo(expectedToken[i]);
- }
- }
-
- @Test
- public void testCreateGeneratingChallengeExtras_notGenerateChallenge() {
- final Bundle credentialExtras = newValidTokenCredentialIntentExtras(201);
- final Bundle savedInstance = new Bundle();
- savedInstance.putBundle(KEY_CREDENTIAL_MODEL, credentialExtras);
- savedInstance.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, false);
- mViewModel.setCredentialModel(savedInstance, new Intent());
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
- }
-
- @Test
- public void testCreateGeneratingChallengeExtras_invalidToken() {
- final Bundle credentialExtras = newOnlySensorValidCredentialIntentExtras(202);
- final Bundle savedInstance = new Bundle();
- savedInstance.putBundle(KEY_CREDENTIAL_MODEL, credentialExtras);
- savedInstance.putBoolean(KEY_IS_GENERATING_CHALLENGE_DURING_CHECKING_CREDENTIAL, true);
- mViewModel.setCredentialModel(savedInstance, new Intent());
-
- // Check createGeneratingChallengeExtras()
- assertThat(mViewModel.createGeneratingChallengeExtras()).isNull();
- }
-
- @Test
- public void testCheckNewCredentialFromActivityResult_invalidChooseLock() {
- final int userId = 107;
- final long gkPwHandle = 3333L;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
- final Intent intent = new Intent();
- intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
-
- // run checkNewCredentialFromActivityResult()
- final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true,
- new ActivityResult(ChooseLockPattern.RESULT_FINISHED + 1, intent));
-
- assertThat(ret).isFalse();
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- }
-
- @Test
- public void testCheckNewCredentialFromActivityResult_invalidConfirmLock() {
- final int userId = 107;
- final long gkPwHandle = 3333L;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
- final Intent intent = new Intent();
- intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
-
- // run checkNewCredentialFromActivityResult()
- final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
- new ActivityResult(Activity.RESULT_OK + 1, intent));
-
- assertThat(ret).isFalse();
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- }
-
- @Test
- public void testCheckNewCredentialFromActivityResult_nullDataChooseLock() {
- final int userId = 108;
- final long gkPwHandle = 4444L;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle)));
-
- // run checkNewCredentialFromActivityResult()
- final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true,
- new ActivityResult(ChooseLockPattern.RESULT_FINISHED, null));
-
- assertThat(ret).isFalse();
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- }
-
- @Test
- public void testCheckNewCredentialFromActivityResult_nullDataConfirmLock() {
- final int userId = 109;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
-
- // run checkNewCredentialFromActivityResult()
- final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
- new ActivityResult(Activity.RESULT_OK, null));
-
- assertThat(ret).isFalse();
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- }
-
- @Test
- public void testCheckNewCredentialFromActivityResult_validChooseLock() {
- final int userId = 108;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
-
- final long gkPwHandle = 6666L;
- final int newSensorId = 50;
- final long newChallenge = 60L;
- setupGenerateChallenge(userId, newSensorId, newChallenge);
- when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
- .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
-
- final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
- doAnswer(invocation -> {
- hasCalledRemoveGkPwHandle.set(true);
- return null;
- }).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
-
- // Run checkNewCredentialFromActivityResult()
- final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
- final boolean ret = mViewModel.checkNewCredentialFromActivityResult(true,
- new ActivityResult(ChooseLockPattern.RESULT_FINISHED, intent));
-
- assertThat(ret).isTrue();
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- assertThat(mViewModel.getToken()).isNotNull();
- assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
- assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
- }
-
- @Test
- public void testCheckNewCredentialFromActivityResult_validConfirmLock() {
- final int userId = 109;
- mViewModel.setCredentialModel(null,
- new Intent().putExtras(newOnlySensorValidCredentialIntentExtras(userId)));
- when(mLockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
- PASSWORD_QUALITY_SOMETHING);
-
- final long gkPwHandle = 5555L;
- final int newSensorId = 80;
- final long newChallenge = 90L;
- setupGenerateChallenge(userId, newSensorId, newChallenge);
- when(mLockPatternUtils.verifyGatekeeperPasswordHandle(gkPwHandle, newChallenge, userId))
- .thenReturn(newGoodCredential(gkPwHandle, new byte[] { 1 }));
-
- final AtomicBoolean hasCalledRemoveGkPwHandle = new AtomicBoolean();
- doAnswer(invocation -> {
- hasCalledRemoveGkPwHandle.set(true);
- return null;
- }).when(mLockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle);
-
- // Run checkNewCredentialFromActivityResult()
- final Intent intent = new Intent().putExtra(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
- final boolean ret = mViewModel.checkNewCredentialFromActivityResult(false,
- new ActivityResult(Activity.RESULT_OK, intent));
-
- assertThat(ret).isTrue();
- assertThat(mViewModel.getGenerateChallengeFailedLiveData().getValue()).isNull();
- assertThat(mViewModel.getToken()).isNotNull();
- assertThat(mChallengeGenerator.mCallbackRunCount).isEqualTo(1);
- assertThat(hasCalledRemoveGkPwHandle.get()).isTrue();
- }
-
- public static class TestChallengeGenerator implements ChallengeGenerator {
- public int mSensorId = -1;
- public int mUserId = UserHandle.myUserId();
- public long mChallenge = INVALID_CHALLENGE;
- public int mCallbackRunCount = 0;
- private GenerateChallengeCallback mCallback;
-
- @Nullable
- @Override
- public GenerateChallengeCallback getCallback() {
- return mCallback;
- }
-
- @Override
- public void setCallback(@Nullable GenerateChallengeCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public void generateChallenge(int userId) {
- final GenerateChallengeCallback callback = mCallback;
- if (callback == null) {
- return;
- }
- callback.onChallengeGenerated(mSensorId, mUserId, mChallenge);
- ++mCallbackRunCount;
- }
- }
-
- private VerifyCredentialResponse newGoodCredential(long gkPwHandle, @NonNull byte[] hat) {
- return new VerifyCredentialResponse.Builder()
- .setGatekeeperPasswordHandle(gkPwHandle)
- .setGatekeeperHAT(hat)
- .build();
- }
-
- private VerifyCredentialResponse newBadCredential(int timeout) {
- if (timeout > 0) {
- return VerifyCredentialResponse.fromTimeout(timeout);
- } else {
- return VerifyCredentialResponse.fromError();
- }
- }
-}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.kt b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.kt
new file mode 100644
index 0000000..3ae4951
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModelTest.kt
@@ -0,0 +1,541 @@
+/*
+ * 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.Activity
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.os.Bundle
+import android.os.SystemClock
+import android.os.UserHandle
+import androidx.activity.result.ActivityResult
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.settings.biometrics.BiometricEnrollBase
+import com.android.settings.biometrics2.ui.model.CredentialModel
+import com.android.settings.biometrics2.ui.model.CredentialModelTest.Companion.newGkPwHandleCredentialIntentExtras
+import com.android.settings.biometrics2.ui.model.CredentialModelTest.Companion.newOnlySensorValidCredentialIntentExtras
+import com.android.settings.biometrics2.ui.model.CredentialModelTest.Companion.newValidTokenCredentialIntentExtras
+import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator
+import com.android.settings.password.ChooseLockPattern
+import com.android.settings.password.ChooseLockSettingsHelper
+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
+import org.mockito.junit.MockitoRule
+import java.util.concurrent.atomic.AtomicBoolean
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AutoCredentialViewModelTest {
+
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+
+ private var challengeGenerator: TestChallengeGenerator = TestChallengeGenerator()
+
+ private lateinit var viewModel: AutoCredentialViewModel
+ private fun newAutoCredentialViewModel(bundle: Bundle?): AutoCredentialViewModel {
+ return AutoCredentialViewModel(
+ ApplicationProvider.getApplicationContext(),
+ lockPatternUtils,
+ challengeGenerator,
+ CredentialModel(bundle, SystemClock.elapsedRealtimeClock())
+ )
+ }
+
+ @Before
+ fun setUp() {
+ challengeGenerator = TestChallengeGenerator()
+ }
+
+ private fun setupGenerateChallenge(userId: Int, newSensorId: Int, newChallenge: Long) {
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+ challengeGenerator.userId = userId
+ challengeGenerator.sensorId = newSensorId
+ challengeGenerator.challenge = newChallenge
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_validCredentialCase() = runTest {
+ val userId = 99
+ viewModel = newAutoCredentialViewModel(newValidTokenCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(backgroundScope)
+ runCurrent()
+
+ // Check viewModel behavior
+ assertThat(action).isEqualTo(CredentialAction.CREDENTIAL_VALID)
+ assertThat(generateFails.size).isEqualTo(0)
+
+ // Check createGeneratingChallengeExtras()
+ assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_needToChooseLock() = runTest {
+ val userId = 100
+ viewModel = newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
+ )
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(backgroundScope)
+ runCurrent()
+
+ // Check viewModel behavior
+ assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK)
+ assertThat(generateFails.size).isEqualTo(0)
+
+ // Check createGeneratingChallengeExtras()
+ assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_needToConfirmLockForSomething() = runTest {
+ val userId = 101
+ viewModel =
+ newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(backgroundScope)
+ runCurrent()
+
+ // Check viewModel behavior
+ assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK)
+ assertThat(generateFails.size).isEqualTo(0)
+
+ // Check createGeneratingChallengeExtras()
+ assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_needToConfirmLockForNumeric() = runTest {
+ val userId = 102
+ viewModel =
+ newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ )
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(backgroundScope)
+ runCurrent()
+
+ // Check viewModel behavior
+ assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK)
+ assertThat(generateFails.size).isEqualTo(0)
+
+ // Check createGeneratingChallengeExtras()
+ assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_needToConfirmLockForAlphabetic() = runTest {
+ val userId = 103
+ viewModel =
+ newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ )
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(this)
+ runCurrent()
+
+ // Check viewModel behavior
+ assertThat(action).isEqualTo(CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK)
+ assertThat(generateFails.size).isEqualTo(0)
+
+ // Check createGeneratingChallengeExtras()
+ assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_generateChallenge() = runTest {
+ val userId = 104
+ val gkPwHandle = 1111L
+ viewModel =
+ newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+ val newSensorId = 10
+ val newChallenge = 20L
+ setupGenerateChallenge(userId, newSensorId, newChallenge)
+ whenever(
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ gkPwHandle,
+ newChallenge,
+ userId
+ )
+ )
+ .thenReturn(newGoodCredential(gkPwHandle, byteArrayOf(1)))
+ val hasCalledRemoveGkPwHandle = AtomicBoolean()
+ Mockito.doAnswer {
+ hasCalledRemoveGkPwHandle.set(true)
+ null
+ }.`when`(lockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle)
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(backgroundScope)
+ runCurrent()
+
+ // Check viewModel behavior
+ assertThat(action).isEqualTo(CredentialAction.IS_GENERATING_CHALLENGE)
+ assertThat(generateFails.size).isEqualTo(0)
+
+ // Check data inside CredentialModel
+ assertThat(viewModel.token).isNotNull()
+ assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
+ assertThat(hasCalledRemoveGkPwHandle.get()).isFalse()
+
+ // Check createGeneratingChallengeExtras()
+ val generatingChallengeExtras = viewModel.createGeneratingChallengeExtras()
+ assertThat(generatingChallengeExtras).isNotNull()
+ assertThat(generatingChallengeExtras!!.getLong(BiometricEnrollBase.EXTRA_KEY_CHALLENGE))
+ .isEqualTo(newChallenge)
+ val tokens =
+ generatingChallengeExtras.getByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
+ assertThat(tokens).isNotNull()
+ assertThat(tokens!!.size).isEqualTo(1)
+ assertThat(tokens[0]).isEqualTo(1)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckCredential_generateChallengeFail() = runTest {
+ backgroundScope.launch {
+ val userId = 104
+ val gkPwHandle = 1111L
+ viewModel =
+ newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+ val newSensorId = 10
+ val newChallenge = 20L
+ setupGenerateChallenge(userId, newSensorId, newChallenge)
+ whenever(
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ gkPwHandle,
+ newChallenge,
+ userId
+ )
+ )
+ .thenReturn(newBadCredential(0))
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run credential check
+ val action = viewModel.checkCredential(this)
+ runCurrent()
+
+ assertThat(action).isEqualTo(CredentialAction.IS_GENERATING_CHALLENGE)
+ assertThat(generateFails.size).isEqualTo(1)
+ assertThat(generateFails[0]).isEqualTo(true)
+ assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
+
+ // Check createGeneratingChallengeExtras()
+ assertThat(viewModel.createGeneratingChallengeExtras()).isNull()
+ }
+ }
+
+ @Test
+ fun testGetUserId_fromIntent() {
+ val userId = 106
+ viewModel = newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+
+ // Get userId
+ assertThat(viewModel.userId).isEqualTo(userId)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testGenerateChallengeAsCredentialActivityResult_invalidChooseLock() = runTest {
+ backgroundScope.launch {
+ val userId = 107
+ val gkPwHandle = 3333L
+ viewModel =
+ newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
+ val intent = Intent()
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run generateChallengeAsCredentialActivityResult()
+ val ret = viewModel.generateChallengeAsCredentialActivityResult(
+ true,
+ ActivityResult(ChooseLockPattern.RESULT_FINISHED + 1, intent),
+ backgroundScope
+ )
+ runCurrent()
+
+ assertThat(ret).isFalse()
+ assertThat(generateFails.size).isEqualTo(0)
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testGenerateChallengeAsCredentialActivityResult_invalidConfirmLock() = runTest {
+ backgroundScope.launch {
+ val userId = 107
+ val gkPwHandle = 3333L
+ viewModel =
+ newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
+ val intent = Intent()
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run generateChallengeAsCredentialActivityResult()
+ val ret = viewModel.generateChallengeAsCredentialActivityResult(
+ false,
+ ActivityResult(Activity.RESULT_OK + 1, intent),
+ backgroundScope
+ )
+ runCurrent()
+
+ assertThat(ret).isFalse()
+ assertThat(generateFails.size).isEqualTo(0)
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testGenerateChallengeAsCredentialActivityResult_nullDataChooseLock() = runTest {
+ val userId = 108
+ val gkPwHandle = 4444L
+ viewModel =
+ newAutoCredentialViewModel(newGkPwHandleCredentialIntentExtras(userId, gkPwHandle))
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run generateChallengeAsCredentialActivityResult()
+ val ret = viewModel.generateChallengeAsCredentialActivityResult(
+ true,
+ ActivityResult(ChooseLockPattern.RESULT_FINISHED, null),
+ backgroundScope
+ )
+ runCurrent()
+
+ assertThat(ret).isFalse()
+ assertThat(generateFails.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testGenerateChallengeAsCredentialActivityResult_nullDataConfirmLock() = runTest {
+ val userId = 109
+ viewModel =
+ newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run generateChallengeAsCredentialActivityResult()
+ val ret = viewModel.generateChallengeAsCredentialActivityResult(
+ false,
+ ActivityResult(Activity.RESULT_OK, null),
+ backgroundScope
+ )
+ runCurrent()
+
+ assertThat(ret).isFalse()
+ assertThat(generateFails.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testGenerateChallengeAsCredentialActivityResult_validChooseLock() = runTest {
+ val userId = 108
+ viewModel =
+ newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+ val gkPwHandle = 6666L
+ val newSensorId = 50
+ val newChallenge = 60L
+ setupGenerateChallenge(userId, newSensorId, newChallenge)
+ whenever(
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ gkPwHandle,
+ newChallenge,
+ userId
+ )
+ )
+ .thenReturn(newGoodCredential(gkPwHandle, byteArrayOf(1)))
+ val hasCalledRemoveGkPwHandle = AtomicBoolean()
+ Mockito.doAnswer {
+ hasCalledRemoveGkPwHandle.set(true)
+ null
+ }.`when`(lockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle)
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run generateChallengeAsCredentialActivityResult()
+ val intent =
+ Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
+ val ret = viewModel.generateChallengeAsCredentialActivityResult(
+ true,
+ ActivityResult(ChooseLockPattern.RESULT_FINISHED, intent),
+ backgroundScope
+ )
+ runCurrent()
+
+ assertThat(ret).isTrue()
+ assertThat(generateFails.size).isEqualTo(0)
+ assertThat(viewModel.token).isNotNull()
+ assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
+ assertThat(hasCalledRemoveGkPwHandle.get()).isTrue()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testGenerateChallengeAsCredentialActivityResult_validConfirmLock() = runTest {
+ val userId = 109
+ viewModel =
+ newAutoCredentialViewModel(newOnlySensorValidCredentialIntentExtras(userId))
+ whenever(lockPatternUtils.getActivePasswordQuality(userId)).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ )
+ val gkPwHandle = 5555L
+ val newSensorId = 80
+ val newChallenge = 90L
+ setupGenerateChallenge(userId, newSensorId, newChallenge)
+ whenever(
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ gkPwHandle,
+ newChallenge,
+ userId
+ )
+ )
+ .thenReturn(newGoodCredential(gkPwHandle, byteArrayOf(1)))
+ val hasCalledRemoveGkPwHandle = AtomicBoolean()
+ Mockito.doAnswer {
+ hasCalledRemoveGkPwHandle.set(true)
+ null
+ }.`when`(lockPatternUtils).removeGatekeeperPasswordHandle(gkPwHandle)
+
+ val generateFails = listOfGenerateChallengeFailedFlow()
+
+ // Run generateChallengeAsCredentialActivityResult()
+ val intent =
+ Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
+ val ret = viewModel.generateChallengeAsCredentialActivityResult(
+ false,
+ ActivityResult(Activity.RESULT_OK, intent),
+ backgroundScope
+ )
+ runCurrent()
+
+ assertThat(ret).isTrue()
+ assertThat(generateFails.size).isEqualTo(0)
+ assertThat(viewModel.token).isNotNull()
+ assertThat(challengeGenerator.callbackRunCount).isEqualTo(1)
+ assertThat(hasCalledRemoveGkPwHandle.get()).isTrue()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun TestScope.listOfGenerateChallengeFailedFlow(): List<Boolean> =
+ mutableListOf<Boolean>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.generateChallengeFailedFlow.toList(it)
+ }
+ }
+
+ class TestChallengeGenerator : ChallengeGenerator {
+ var sensorId = -1
+ var userId = UserHandle.myUserId()
+ var challenge = CredentialModel.INVALID_CHALLENGE
+ var callbackRunCount = 0
+
+ private var _callback: AutoCredentialViewModel.GenerateChallengeCallback? = null
+
+ override fun getCallback(): AutoCredentialViewModel.GenerateChallengeCallback? {
+ return _callback
+ }
+
+ override fun setCallback(callback: AutoCredentialViewModel.GenerateChallengeCallback?) {
+ _callback = callback
+ }
+
+ override fun generateChallenge(userId: Int) {
+ val callback = _callback ?: return
+ callback.onChallengeGenerated(sensorId, this.userId, challenge)
+ ++callbackRunCount
+ }
+ }
+
+ private fun newGoodCredential(gkPwHandle: Long, hat: ByteArray): VerifyCredentialResponse {
+ return VerifyCredentialResponse.Builder()
+ .setGatekeeperPasswordHandle(gkPwHandle)
+ .setGatekeeperHAT(hat)
+ .build()
+ }
+
+ private fun newBadCredential(timeout: Int): VerifyCredentialResponse {
+ return if (timeout > 0) {
+ VerifyCredentialResponse.fromTimeout(timeout)
+ } else {
+ VerifyCredentialResponse.fromError()
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.kt b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.kt
index 9f339de..bee91c9 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollmentViewModelTest.kt
@@ -23,12 +23,20 @@
import androidx.activity.result.ActivityResult
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.biometrics.BiometricEnrollBase
import com.android.settings.biometrics2.data.repository.FingerprintRepository
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newAllFalseRequest
+import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newIsSuwRequest
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.newFingerprintRepository
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints
-import com.android.settings.testutils.InstantTaskExecutorRule
-import com.google.common.truth.Truth
+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
@@ -42,8 +50,6 @@
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
- @get:Rule val taskExecutorRule = InstantTaskExecutorRule()
-
private val application: Application
get() = ApplicationProvider.getApplicationContext()
@@ -69,12 +75,12 @@
@Test
fun testGetRequest() {
- Truth.assertThat(viewModel.request).isNotNull()
+ assertThat(viewModel.request).isNotNull()
}
@Test
fun testIsWaitingActivityResultDefaultFalse() {
- Truth.assertThat(viewModel.isWaitingActivityResult.value).isFalse()
+ assertThat(viewModel.isWaitingActivityResult.value).isFalse()
}
@@ -83,8 +89,8 @@
val retResult = viewModel.getOverrideActivityResult(
ActivityResult(22, null), null
)
- Truth.assertThat(retResult).isNotNull()
- Truth.assertThat(retResult.data).isNull()
+ assertThat(retResult).isNotNull()
+ assertThat(retResult.data).isNull()
}
@Test
@@ -93,8 +99,8 @@
val retResult = viewModel.getOverrideActivityResult(
ActivityResult(33, intent), null
)
- Truth.assertThat(retResult).isNotNull()
- Truth.assertThat(retResult.data).isEqualTo(intent)
+ assertThat(retResult).isNotNull()
+ assertThat(retResult.data).isEqualTo(intent)
}
@Test
@@ -106,8 +112,8 @@
ActivityResult(33, null), extra
)
- Truth.assertThat(retResult).isNotNull()
- Truth.assertThat(retResult.data).isNull()
+ assertThat(retResult).isNotNull()
+ assertThat(retResult.data).isNull()
}
@Test
@@ -124,16 +130,16 @@
val retResult = viewModel.getOverrideActivityResult(
ActivityResult(33, null), extra
)
- Truth.assertThat(retResult).isNotNull()
+ assertThat(retResult).isNotNull()
val retIntent = retResult.data
- Truth.assertThat(retIntent).isNotNull()
+ assertThat(retIntent).isNotNull()
val retExtra = retIntent!!.extras
- Truth.assertThat(retExtra).isNotNull()
- Truth.assertThat(retExtra!!.size).isEqualTo(extra.size)
- Truth.assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
- Truth.assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
+ assertThat(retExtra).isNotNull()
+ assertThat(retExtra!!.size).isEqualTo(extra.size)
+ assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
+ assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
}
@Test
@@ -149,15 +155,15 @@
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
- Truth.assertThat(retResult).isNotNull()
+ assertThat(retResult).isNotNull()
val retIntent = retResult.data
- Truth.assertThat(retIntent).isNotNull()
+ assertThat(retIntent).isNotNull()
val retExtra = retIntent!!.extras
- Truth.assertThat(retExtra).isNotNull()
- Truth.assertThat(retExtra!!.size).isEqualTo(intent.extras!!.size)
- Truth.assertThat(retExtra.getString(key2)).isEqualTo(intent.extras!!.getString(key2))
+ assertThat(retExtra).isNotNull()
+ assertThat(retExtra!!.size).isEqualTo(intent.extras!!.size)
+ assertThat(retExtra.getString(key2)).isEqualTo(intent.extras!!.getString(key2))
}
@Test
@@ -177,17 +183,17 @@
viewModel.isNewFingerprintAdded = true
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
- Truth.assertThat(retResult).isNotNull()
+ assertThat(retResult).isNotNull()
val retIntent = retResult.data
- Truth.assertThat(retIntent).isNotNull()
+ assertThat(retIntent).isNotNull()
val retExtra = retIntent!!.extras
- Truth.assertThat(retExtra).isNotNull()
- Truth.assertThat(retExtra!!.size).isEqualTo(extra.size + intent.extras!!.size)
- Truth.assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
- Truth.assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
- Truth.assertThat(retExtra.getLong(key3)).isEqualTo(intent.extras!!.getLong(key3))
+ assertThat(retExtra).isNotNull()
+ assertThat(retExtra!!.size).isEqualTo(extra.size + intent.extras!!.size)
+ assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
+ assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
+ assertThat(retExtra.getLong(key3)).isEqualTo(intent.extras!!.getLong(key3))
}
@Test
@@ -205,18 +211,120 @@
)
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 0)
- Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
+ assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 1)
- Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
+ assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 2)
- Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
+ assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 3)
- Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
+ assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 4)
- Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
+ assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testSetResultFlow_defaultEmpty() = runTest {
+ val activityResults = listOfSetResultFlow()
+
+ runCurrent()
+
+ assertThat(activityResults.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckFinishActivityDuringOnPause_doNothingIfIsSuw() = runTest {
+ viewModel = FingerprintEnrollmentViewModel(
+ application,
+ fingerprintRepository,
+ newIsSuwRequest(application)
+ )
+
+ val activityResults = listOfSetResultFlow()
+
+ viewModel.checkFinishActivityDuringOnPause(
+ isActivityFinishing = false,
+ isChangingConfigurations = false,
+ scope = this
+ )
+ runCurrent()
+
+ assertThat(activityResults.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckFinishActivityDuringOnPause_doNothingIfIsWaitingActivity() = runTest {
+ val activityResults = listOfSetResultFlow()
+
+ viewModel.isWaitingActivityResult.value = true
+ viewModel.checkFinishActivityDuringOnPause(
+ isActivityFinishing = false,
+ isChangingConfigurations = false,
+ scope = this
+ )
+ runCurrent()
+
+ assertThat(activityResults.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckFinishActivityDuringOnPause_doNothingIfIsActivityFinishing() = runTest {
+ val activityResults = listOfSetResultFlow()
+
+ viewModel.checkFinishActivityDuringOnPause(
+ isActivityFinishing = true,
+ isChangingConfigurations = false,
+ scope = this
+ )
+ runCurrent()
+
+ assertThat(activityResults.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckFinishActivityDuringOnPause_doNothingIfIsChangingConfigurations() = runTest {
+ val activityResults = listOfSetResultFlow()
+
+ viewModel.checkFinishActivityDuringOnPause(
+ isActivityFinishing = false,
+ isChangingConfigurations = true,
+ scope = this
+ )
+ runCurrent()
+
+ assertThat(activityResults.size).isEqualTo(0)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun testCheckFinishActivityDuringOnPause_defaultFinishSelf() = runTest {
+ val activityResults = listOfSetResultFlow()
+
+ viewModel.checkFinishActivityDuringOnPause(
+ isActivityFinishing = false,
+ isChangingConfigurations = false,
+ scope = backgroundScope
+ )
+ runCurrent()
+
+ assertThat(activityResults.size).isEqualTo(1)
+ assertThat(activityResults[0].resultCode).isEqualTo(BiometricEnrollBase.RESULT_TIMEOUT)
+ assertThat(activityResults[0].data).isEqualTo(null)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun TestScope.listOfSetResultFlow(): List<ActivityResult> =
+ mutableListOf<ActivityResult>().also {
+ backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
+ viewModel.setResultFlow.toList(it)
+ }
+ }
}