[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
This commit is contained in:
@@ -28,6 +28,7 @@ import androidx.lifecycle.viewmodel.CreationExtras;
|
|||||||
import com.android.internal.widget.LockPatternUtils;
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
||||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
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.model.EnrollmentRequest;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
||||||
@@ -54,8 +55,8 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
|||||||
new CreationExtras.Key<ChallengeGenerator>() {};
|
new CreationExtras.Key<ChallengeGenerator>() {};
|
||||||
public static final CreationExtras.Key<EnrollmentRequest> ENROLLMENT_REQUEST_KEY =
|
public static final CreationExtras.Key<EnrollmentRequest> ENROLLMENT_REQUEST_KEY =
|
||||||
new CreationExtras.Key<EnrollmentRequest>() {};
|
new CreationExtras.Key<EnrollmentRequest>() {};
|
||||||
public static final CreationExtras.Key<Integer> USER_ID_KEY =
|
public static final CreationExtras.Key<CredentialModel> CREDENTIAL_MODEL_KEY =
|
||||||
new CreationExtras.Key<Integer>() {};
|
new CreationExtras.Key<CredentialModel>() {};
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
@@ -76,9 +77,10 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
|||||||
final LockPatternUtils lockPatternUtils =
|
final LockPatternUtils lockPatternUtils =
|
||||||
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
|
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
|
||||||
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR_KEY);
|
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,
|
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
|
||||||
challengeGenerator);
|
challengeGenerator, credentialModel);
|
||||||
}
|
}
|
||||||
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
|
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
|
||||||
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
|
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
|
||||||
@@ -93,10 +95,10 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
|||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
||||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
||||||
final Integer userId = extras.get(USER_ID_KEY);
|
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||||
if (repository != null && request != null && userId != null) {
|
if (repository != null && request != null && credentialModel != null) {
|
||||||
return (T) new FingerprintEnrollIntroViewModel(application, repository, request,
|
return (T) new FingerprintEnrollIntroViewModel(application, repository, request,
|
||||||
userId);
|
credentialModel.getUserId());
|
||||||
}
|
}
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) {
|
} else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) {
|
||||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||||
@@ -105,27 +107,27 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
|||||||
return (T) new FingerprintEnrollmentViewModel(application, repository, request);
|
return (T) new FingerprintEnrollmentViewModel(application, repository, request);
|
||||||
}
|
}
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
|
} else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
|
||||||
final Integer userId = extras.get(USER_ID_KEY);
|
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||||
if (userId != null) {
|
if (credentialModel != null) {
|
||||||
return (T) new FingerprintEnrollProgressViewModel(application,
|
return (T) new FingerprintEnrollProgressViewModel(application,
|
||||||
new FingerprintUpdater(application), userId);
|
new FingerprintUpdater(application), credentialModel.getUserId());
|
||||||
}
|
}
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) {
|
} 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(
|
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
||||||
application);
|
application);
|
||||||
if (fingerprint != null && userId != null) {
|
if (fingerprint != null && credentialModel != null) {
|
||||||
return (T) new FingerprintEnrollEnrollingViewModel(application, userId,
|
return (T) new FingerprintEnrollEnrollingViewModel(application,
|
||||||
fingerprint);
|
credentialModel.getUserId(), fingerprint);
|
||||||
}
|
}
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollFinishViewModel.class)) {
|
} 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 EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
||||||
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
||||||
application);
|
application);
|
||||||
if (fingerprint != null && userId != null && request != null) {
|
if (fingerprint != null && credentialModel != null && request != null) {
|
||||||
return (T) new FingerprintEnrollFinishViewModel(application, userId, request,
|
return (T) new FingerprintEnrollFinishViewModel(application,
|
||||||
fingerprint);
|
credentialModel.getUserId(), request, fingerprint);
|
||||||
}
|
}
|
||||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) {
|
} else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) {
|
||||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
||||||
|
@@ -80,20 +80,6 @@ class CredentialModel(bundle: Bundle?, private val clock: Clock) {
|
|||||||
val isValidToken: Boolean
|
val isValidToken: Boolean
|
||||||
get() = token != null
|
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 */
|
/** Returns a string representation of the object */
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val gkPwHandleLen = "$gkPwHandle".length
|
val gkPwHandleLen = "$gkPwHandle".length
|
||||||
|
@@ -44,16 +44,13 @@ import com.android.settings.Utils
|
|||||||
import com.android.settings.biometrics.BiometricEnrollBase
|
import com.android.settings.biometrics.BiometricEnrollBase
|
||||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory
|
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory
|
||||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR_KEY
|
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.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.CredentialModel
|
||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel
|
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.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.DeviceFoldedViewModel
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
||||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE
|
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE
|
||||||
@@ -170,7 +167,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
autoCredentialViewModel.setCredentialModel(savedInstanceState, intent)
|
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
setTheme(viewModel.request.theme)
|
setTheme(viewModel.request.theme)
|
||||||
@@ -219,14 +215,23 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// observe LiveData
|
collectFlows()
|
||||||
viewModel.setResultLiveData.observe(this) {
|
}
|
||||||
result: ActivityResult -> onSetActivityResult(result)
|
|
||||||
}
|
private fun collectFlows() {
|
||||||
autoCredentialViewModel.generateChallengeFailedLiveData.observe(this) {
|
|
||||||
_: Boolean -> onGenerateChallengeFailed()
|
|
||||||
}
|
|
||||||
lifecycleScope.launch {
|
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) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
errorDialogViewModel.newDialogFlow.collect {
|
errorDialogViewModel.newDialogFlow.collect {
|
||||||
Log.d(TAG, "newErrorDialogFlow($it)")
|
Log.d(TAG, "newErrorDialogFlow($it)")
|
||||||
@@ -236,8 +241,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
errorDialogViewModel.setResultFlow.collect {
|
errorDialogViewModel.setResultFlow.collect {
|
||||||
Log.d(TAG, "errorDialogSetResultFlow($it)")
|
Log.d(TAG, "errorDialogSetResultFlow($it)")
|
||||||
@@ -408,10 +411,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onGenerateChallengeFailed() {
|
|
||||||
onSetActivityResult(ActivityResult(RESULT_CANCELED, null))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSetActivityResult(result: ActivityResult) {
|
private fun onSetActivityResult(result: ActivityResult) {
|
||||||
val challengeExtras: Bundle? = autoCredentialViewModel.createGeneratingChallengeExtras()
|
val challengeExtras: Bundle? = autoCredentialViewModel.createGeneratingChallengeExtras()
|
||||||
val overrideResult: ActivityResult = viewModel.getOverrideActivityResult(
|
val overrideResult: ActivityResult = viewModel.getOverrideActivityResult(
|
||||||
@@ -428,8 +427,8 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkCredential() {
|
private fun checkCredential() {
|
||||||
when (autoCredentialViewModel.checkCredential()) {
|
when (autoCredentialViewModel.checkCredential(lifecycleScope)) {
|
||||||
CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK -> {
|
CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK -> {
|
||||||
val intent: Intent = autoCredentialViewModel.createChooseLockIntent(
|
val intent: Intent = autoCredentialViewModel.createChooseLockIntent(
|
||||||
this,
|
this,
|
||||||
viewModel.request.isSuw,
|
viewModel.request.isSuw,
|
||||||
@@ -442,7 +441,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK -> {
|
CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK -> {
|
||||||
val launched: Boolean = autoCredentialViewModel.createConfirmLockLauncher(
|
val launched: Boolean = autoCredentialViewModel.createConfirmLockLauncher(
|
||||||
this,
|
this,
|
||||||
LAUNCH_CONFIRM_LOCK_ACTIVITY,
|
LAUNCH_CONFIRM_LOCK_ACTIVITY,
|
||||||
@@ -459,21 +458,24 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
CREDENTIAL_VALID,
|
CredentialAction.CREDENTIAL_VALID,
|
||||||
CREDENTIAL_IS_GENERATING_CHALLENGE -> {}
|
CredentialAction.IS_GENERATING_CHALLENGE -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onChooseOrConfirmLockResult(isChooseLock: Boolean, activityResult: ActivityResult) {
|
private fun onChooseOrConfirmLockResult(
|
||||||
|
isChooseLock: Boolean,
|
||||||
|
activityResult: ActivityResult
|
||||||
|
) {
|
||||||
if (!viewModel.isWaitingActivityResult.compareAndSet(true, false)) {
|
if (!viewModel.isWaitingActivityResult.compareAndSet(true, false)) {
|
||||||
Log.w(TAG, "isChooseLock:$isChooseLock, fail to unset waiting flag")
|
Log.w(TAG, "isChooseLock:$isChooseLock, fail to unset waiting flag")
|
||||||
}
|
}
|
||||||
if (autoCredentialViewModel.checkNewCredentialFromActivityResult(
|
if (!autoCredentialViewModel.generateChallengeAsCredentialActivityResult(
|
||||||
isChooseLock, activityResult
|
isChooseLock,
|
||||||
|
activityResult,
|
||||||
|
lifecycleScope
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out)
|
|
||||||
} else {
|
|
||||||
onSetActivityResult(activityResult)
|
onSetActivityResult(activityResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,7 +575,11 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
viewModel.checkFinishActivityDuringOnPause(isFinishing, isChangingConfigurations)
|
viewModel.checkFinishActivityDuringOnPause(
|
||||||
|
isFinishing,
|
||||||
|
isChangingConfigurations,
|
||||||
|
lifecycleScope
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -596,17 +602,14 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val defaultViewModelCreationExtras: CreationExtras
|
override val defaultViewModelCreationExtras: CreationExtras
|
||||||
get() {
|
get() = MutableCreationExtras(super.defaultViewModelCreationExtras).also {
|
||||||
val fingerprintRepository = featureFactory.biometricsRepositoryProvider
|
it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(
|
||||||
.getFingerprintRepository(application)!!
|
featureFactory.biometricsRepositoryProvider.getFingerprintRepository(application)!!
|
||||||
val credentialModel = CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
|
)
|
||||||
|
it[ENROLLMENT_REQUEST_KEY] =
|
||||||
return MutableCreationExtras(super.defaultViewModelCreationExtras).also {
|
EnrollmentRequest(intent, applicationContext, this is SetupActivity)
|
||||||
it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(fingerprintRepository)
|
it[CREDENTIAL_MODEL_KEY] =
|
||||||
it[ENROLLMENT_REQUEST_KEY] =
|
CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
|
||||||
EnrollmentRequest(intent, applicationContext, this is SetupActivity)
|
|
||||||
it[USER_ID_KEY] = credentialModel.userId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
|
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
|
||||||
@@ -630,11 +633,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
|||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
autoCredentialViewModel.onSaveInstanceState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val DEBUG = false
|
private const val DEBUG = false
|
||||||
private const val TAG = "FingerprintEnrollmentActivity"
|
private const val TAG = "FingerprintEnrollmentActivity"
|
||||||
|
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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
|
||||||
|
}
|
@@ -23,8 +23,6 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import com.android.settings.biometrics.BiometricEnrollBase
|
import com.android.settings.biometrics.BiometricEnrollBase
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY
|
||||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
|
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
|
||||||
@@ -32,6 +30,11 @@ import com.android.settings.biometrics2.data.repository.FingerprintRepository
|
|||||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
||||||
import kotlinx.atomicfu.AtomicBoolean
|
import kotlinx.atomicfu.AtomicBoolean
|
||||||
import kotlinx.atomicfu.atomic
|
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
|
* Fingerprint enrollment view model implementation
|
||||||
@@ -44,9 +47,9 @@ class FingerprintEnrollmentViewModel(
|
|||||||
|
|
||||||
val isWaitingActivityResult: AtomicBoolean = atomic(false)
|
val isWaitingActivityResult: AtomicBoolean = atomic(false)
|
||||||
|
|
||||||
private val _setResultLiveData = MutableLiveData<ActivityResult>()
|
private val _setResultFlow = MutableSharedFlow<ActivityResult>()
|
||||||
val setResultLiveData: LiveData<ActivityResult>
|
val setResultFlow: SharedFlow<ActivityResult>
|
||||||
get() = _setResultLiveData
|
get() = _setResultFlow.asSharedFlow()
|
||||||
|
|
||||||
var isNewFingerprintAdded = false
|
var isNewFingerprintAdded = false
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -94,16 +97,17 @@ class FingerprintEnrollmentViewModel(
|
|||||||
*/
|
*/
|
||||||
fun checkFinishActivityDuringOnPause(
|
fun checkFinishActivityDuringOnPause(
|
||||||
isActivityFinishing: Boolean,
|
isActivityFinishing: Boolean,
|
||||||
isChangingConfigurations: Boolean
|
isChangingConfigurations: Boolean,
|
||||||
|
scope: CoroutineScope
|
||||||
) {
|
) {
|
||||||
if (isChangingConfigurations || isActivityFinishing || request.isSuw
|
if (isChangingConfigurations || isActivityFinishing || request.isSuw
|
||||||
|| isWaitingActivityResult.value
|
|| isWaitingActivityResult.value
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_setResultLiveData.postValue(
|
scope.launch {
|
||||||
ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)
|
_setResultFlow.emit(ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null))
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,23 +137,23 @@ class FingerprintEnrollmentViewModel(
|
|||||||
* Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
|
* Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
|
||||||
*/
|
*/
|
||||||
fun updateFingerprintSuggestionEnableState(userId: Int) {
|
fun updateFingerprintSuggestionEnableState(userId: Int) {
|
||||||
val enrolled = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
|
|
||||||
// Only show "Add another fingerprint" if the user already enrolled one.
|
// 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
|
// "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
|
// fingerprints. If the user already added more than one fingerprint, they already know
|
||||||
// to add multiple fingerprints so we don't show the suggestion.
|
// 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(
|
getApplication<Application>().packageManager.setComponentEnabledSetting(
|
||||||
ComponentName(
|
ComponentName(
|
||||||
getApplication(),
|
getApplication(),
|
||||||
FINGERPRINT_SUGGESTION_ACTIVITY
|
FINGERPRINT_SUGGESTION_ACTIVITY
|
||||||
),
|
),
|
||||||
if (enrolled == 1)
|
state,
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
||||||
else
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
|
||||||
PackageManager.DONT_KILL_APP
|
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 {
|
companion object {
|
||||||
|
@@ -38,22 +38,6 @@ class CredentialModelTest {
|
|||||||
Truth.assertThat(credentialModel.userId).isEqualTo(UserHandle.myUserId())
|
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 {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newCredentialModelIntentExtras(
|
fun newCredentialModelIntentExtras(
|
||||||
@@ -148,36 +132,5 @@ class CredentialModelTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -23,12 +23,20 @@ import android.os.Bundle
|
|||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
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.data.repository.FingerprintRepository
|
||||||
import com.android.settings.biometrics2.utils.EnrollmentRequestUtils.newAllFalseRequest
|
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.newFingerprintRepository
|
||||||
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints
|
import com.android.settings.biometrics2.utils.FingerprintRepositoryUtils.setupFingerprintEnrolledFingerprints
|
||||||
import com.android.settings.testutils.InstantTaskExecutorRule
|
import com.google.common.truth.Truth.assertThat
|
||||||
import com.google.common.truth.Truth
|
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.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -42,8 +50,6 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
|
|
||||||
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
|
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
|
||||||
|
|
||||||
@get:Rule val taskExecutorRule = InstantTaskExecutorRule()
|
|
||||||
|
|
||||||
private val application: Application
|
private val application: Application
|
||||||
get() = ApplicationProvider.getApplicationContext()
|
get() = ApplicationProvider.getApplicationContext()
|
||||||
|
|
||||||
@@ -69,12 +75,12 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetRequest() {
|
fun testGetRequest() {
|
||||||
Truth.assertThat(viewModel.request).isNotNull()
|
assertThat(viewModel.request).isNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIsWaitingActivityResultDefaultFalse() {
|
fun testIsWaitingActivityResultDefaultFalse() {
|
||||||
Truth.assertThat(viewModel.isWaitingActivityResult.value).isFalse()
|
assertThat(viewModel.isWaitingActivityResult.value).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -83,8 +89,8 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
val retResult = viewModel.getOverrideActivityResult(
|
val retResult = viewModel.getOverrideActivityResult(
|
||||||
ActivityResult(22, null), null
|
ActivityResult(22, null), null
|
||||||
)
|
)
|
||||||
Truth.assertThat(retResult).isNotNull()
|
assertThat(retResult).isNotNull()
|
||||||
Truth.assertThat(retResult.data).isNull()
|
assertThat(retResult.data).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -93,8 +99,8 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
val retResult = viewModel.getOverrideActivityResult(
|
val retResult = viewModel.getOverrideActivityResult(
|
||||||
ActivityResult(33, intent), null
|
ActivityResult(33, intent), null
|
||||||
)
|
)
|
||||||
Truth.assertThat(retResult).isNotNull()
|
assertThat(retResult).isNotNull()
|
||||||
Truth.assertThat(retResult.data).isEqualTo(intent)
|
assertThat(retResult.data).isEqualTo(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -106,8 +112,8 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
ActivityResult(33, null), extra
|
ActivityResult(33, null), extra
|
||||||
)
|
)
|
||||||
|
|
||||||
Truth.assertThat(retResult).isNotNull()
|
assertThat(retResult).isNotNull()
|
||||||
Truth.assertThat(retResult.data).isNull()
|
assertThat(retResult.data).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -124,16 +130,16 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
val retResult = viewModel.getOverrideActivityResult(
|
val retResult = viewModel.getOverrideActivityResult(
|
||||||
ActivityResult(33, null), extra
|
ActivityResult(33, null), extra
|
||||||
)
|
)
|
||||||
Truth.assertThat(retResult).isNotNull()
|
assertThat(retResult).isNotNull()
|
||||||
|
|
||||||
val retIntent = retResult.data
|
val retIntent = retResult.data
|
||||||
Truth.assertThat(retIntent).isNotNull()
|
assertThat(retIntent).isNotNull()
|
||||||
|
|
||||||
val retExtra = retIntent!!.extras
|
val retExtra = retIntent!!.extras
|
||||||
Truth.assertThat(retExtra).isNotNull()
|
assertThat(retExtra).isNotNull()
|
||||||
Truth.assertThat(retExtra!!.size).isEqualTo(extra.size)
|
assertThat(retExtra!!.size).isEqualTo(extra.size)
|
||||||
Truth.assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
|
assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
|
||||||
Truth.assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
|
assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -149,15 +155,15 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
|
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
|
||||||
|
|
||||||
Truth.assertThat(retResult).isNotNull()
|
assertThat(retResult).isNotNull()
|
||||||
|
|
||||||
val retIntent = retResult.data
|
val retIntent = retResult.data
|
||||||
Truth.assertThat(retIntent).isNotNull()
|
assertThat(retIntent).isNotNull()
|
||||||
|
|
||||||
val retExtra = retIntent!!.extras
|
val retExtra = retIntent!!.extras
|
||||||
Truth.assertThat(retExtra).isNotNull()
|
assertThat(retExtra).isNotNull()
|
||||||
Truth.assertThat(retExtra!!.size).isEqualTo(intent.extras!!.size)
|
assertThat(retExtra!!.size).isEqualTo(intent.extras!!.size)
|
||||||
Truth.assertThat(retExtra.getString(key2)).isEqualTo(intent.extras!!.getString(key2))
|
assertThat(retExtra.getString(key2)).isEqualTo(intent.extras!!.getString(key2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -177,17 +183,17 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
viewModel.isNewFingerprintAdded = true
|
viewModel.isNewFingerprintAdded = true
|
||||||
|
|
||||||
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
|
val retResult = viewModel.getOverrideActivityResult(ActivityResult(33, intent), extra)
|
||||||
Truth.assertThat(retResult).isNotNull()
|
assertThat(retResult).isNotNull()
|
||||||
|
|
||||||
val retIntent = retResult.data
|
val retIntent = retResult.data
|
||||||
Truth.assertThat(retIntent).isNotNull()
|
assertThat(retIntent).isNotNull()
|
||||||
|
|
||||||
val retExtra = retIntent!!.extras
|
val retExtra = retIntent!!.extras
|
||||||
Truth.assertThat(retExtra).isNotNull()
|
assertThat(retExtra).isNotNull()
|
||||||
Truth.assertThat(retExtra!!.size).isEqualTo(extra.size + intent.extras!!.size)
|
assertThat(retExtra!!.size).isEqualTo(extra.size + intent.extras!!.size)
|
||||||
Truth.assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
|
assertThat(retExtra.getString(key1)).isEqualTo(extra.getString(key1))
|
||||||
Truth.assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
|
assertThat(retExtra.getInt(key2)).isEqualTo(extra.getInt(key2))
|
||||||
Truth.assertThat(retExtra.getLong(key3)).isEqualTo(intent.extras!!.getLong(key3))
|
assertThat(retExtra.getLong(key3)).isEqualTo(intent.extras!!.getLong(key3))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -205,18 +211,120 @@ class FingerprintEnrollmentViewModelTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 0)
|
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 0)
|
||||||
Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 1)
|
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 1)
|
||||||
Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 2)
|
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 2)
|
||||||
Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
assertThat(viewModel.isMaxEnrolledReached(uid)).isFalse()
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 3)
|
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 3)
|
||||||
Truth.assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
|
assertThat(viewModel.isMaxEnrolledReached(uid)).isTrue()
|
||||||
|
|
||||||
setupFingerprintEnrolledFingerprints(fingerprintManager, uid, 4)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user