Merge "[BiometricsV2] Refactor AutoCredentialViewModel" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
538eafcbf2
@@ -28,6 +28,7 @@ import androidx.lifecycle.viewmodel.CreationExtras;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
|
||||
import com.android.settings.biometrics2.data.repository.FingerprintRepository;
|
||||
import com.android.settings.biometrics2.ui.model.CredentialModel;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.ChallengeGenerator;
|
||||
@@ -54,8 +55,8 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
new CreationExtras.Key<ChallengeGenerator>() {};
|
||||
public static final CreationExtras.Key<EnrollmentRequest> ENROLLMENT_REQUEST_KEY =
|
||||
new CreationExtras.Key<EnrollmentRequest>() {};
|
||||
public static final CreationExtras.Key<Integer> USER_ID_KEY =
|
||||
new CreationExtras.Key<Integer>() {};
|
||||
public static final CreationExtras.Key<CredentialModel> CREDENTIAL_MODEL_KEY =
|
||||
new CreationExtras.Key<CredentialModel>() {};
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -76,9 +77,10 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
final LockPatternUtils lockPatternUtils =
|
||||
featureFactory.getSecurityFeatureProvider().getLockPatternUtils(application);
|
||||
final ChallengeGenerator challengeGenerator = extras.get(CHALLENGE_GENERATOR_KEY);
|
||||
if (challengeGenerator != null) {
|
||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||
if (challengeGenerator != null && credentialModel != null) {
|
||||
return (T) new AutoCredentialViewModel(application, lockPatternUtils,
|
||||
challengeGenerator);
|
||||
challengeGenerator, credentialModel);
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(DeviceFoldedViewModel.class)) {
|
||||
return (T) new DeviceFoldedViewModel(new ScreenSizeFoldProvider(application),
|
||||
@@ -93,10 +95,10 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollIntroViewModel.class)) {
|
||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
||||
final Integer userId = extras.get(USER_ID_KEY);
|
||||
if (repository != null && request != null && userId != null) {
|
||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||
if (repository != null && request != null && credentialModel != null) {
|
||||
return (T) new FingerprintEnrollIntroViewModel(application, repository, request,
|
||||
userId);
|
||||
credentialModel.getUserId());
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollmentViewModel.class)) {
|
||||
final FingerprintRepository repository = provider.getFingerprintRepository(application);
|
||||
@@ -105,27 +107,27 @@ public class BiometricsViewModelFactory implements ViewModelProvider.Factory {
|
||||
return (T) new FingerprintEnrollmentViewModel(application, repository, request);
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollProgressViewModel.class)) {
|
||||
final Integer userId = extras.get(USER_ID_KEY);
|
||||
if (userId != null) {
|
||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||
if (credentialModel != null) {
|
||||
return (T) new FingerprintEnrollProgressViewModel(application,
|
||||
new FingerprintUpdater(application), userId);
|
||||
new FingerprintUpdater(application), credentialModel.getUserId());
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollEnrollingViewModel.class)) {
|
||||
final Integer userId = extras.get(USER_ID_KEY);
|
||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
||||
application);
|
||||
if (fingerprint != null && userId != null) {
|
||||
return (T) new FingerprintEnrollEnrollingViewModel(application, userId,
|
||||
fingerprint);
|
||||
if (fingerprint != null && credentialModel != null) {
|
||||
return (T) new FingerprintEnrollEnrollingViewModel(application,
|
||||
credentialModel.getUserId(), fingerprint);
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollFinishViewModel.class)) {
|
||||
final Integer userId = extras.get(USER_ID_KEY);
|
||||
final CredentialModel credentialModel = extras.get(CREDENTIAL_MODEL_KEY);
|
||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
||||
final FingerprintRepository fingerprint = provider.getFingerprintRepository(
|
||||
application);
|
||||
if (fingerprint != null && userId != null && request != null) {
|
||||
return (T) new FingerprintEnrollFinishViewModel(application, userId, request,
|
||||
fingerprint);
|
||||
if (fingerprint != null && credentialModel != null && request != null) {
|
||||
return (T) new FingerprintEnrollFinishViewModel(application,
|
||||
credentialModel.getUserId(), request, fingerprint);
|
||||
}
|
||||
} else if (modelClass.isAssignableFrom(FingerprintEnrollErrorDialogViewModel.class)) {
|
||||
final EnrollmentRequest request = extras.get(ENROLLMENT_REQUEST_KEY);
|
||||
|
@@ -80,20 +80,6 @@ class CredentialModel(bundle: Bundle?, private val clock: Clock) {
|
||||
val isValidToken: Boolean
|
||||
get() = token != null
|
||||
|
||||
val bundle: Bundle
|
||||
/**
|
||||
* Get a bundle which can be used to recreate CredentialModel
|
||||
*/
|
||||
get() {
|
||||
val bundle = Bundle()
|
||||
bundle.putInt(EXTRA_USER_ID, userId)
|
||||
bundle.putLong(EXTRA_KEY_CHALLENGE, challenge)
|
||||
bundle.putByteArray(EXTRA_KEY_CHALLENGE_TOKEN, token)
|
||||
bundle.putLong(EXTRA_KEY_GK_PW_HANDLE, gkPwHandle)
|
||||
return bundle
|
||||
}
|
||||
|
||||
|
||||
/** Returns a string representation of the object */
|
||||
override fun toString(): String {
|
||||
val gkPwHandleLen = "$gkPwHandle".length
|
||||
|
@@ -44,16 +44,13 @@ import com.android.settings.Utils
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory
|
||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CHALLENGE_GENERATOR_KEY
|
||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.CREDENTIAL_MODEL_KEY
|
||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.ENROLLMENT_REQUEST_KEY
|
||||
import com.android.settings.biometrics2.factory.BiometricsViewModelFactory.USER_ID_KEY
|
||||
import com.android.settings.biometrics2.ui.model.CredentialModel
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_IS_GENERATING_CHALLENGE
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.CREDENTIAL_VALID
|
||||
import com.android.settings.biometrics2.ui.viewmodel.AutoCredentialViewModel.FingerprintChallengeGenerator
|
||||
import com.android.settings.biometrics2.ui.viewmodel.CredentialAction
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceFoldedViewModel
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel.FINGERPRINT_ENROLL_ENROLLING_ACTION_DONE
|
||||
@@ -170,7 +167,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
autoCredentialViewModel.setCredentialModel(savedInstanceState, intent)
|
||||
|
||||
// Theme
|
||||
setTheme(viewModel.request.theme)
|
||||
@@ -219,14 +215,23 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// observe LiveData
|
||||
viewModel.setResultLiveData.observe(this) {
|
||||
result: ActivityResult -> onSetActivityResult(result)
|
||||
}
|
||||
autoCredentialViewModel.generateChallengeFailedLiveData.observe(this) {
|
||||
_: Boolean -> onGenerateChallengeFailed()
|
||||
}
|
||||
collectFlows()
|
||||
}
|
||||
|
||||
private fun collectFlows() {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.setResultFlow.collect {
|
||||
Log.d(TAG, "setResultLiveData($it)")
|
||||
onSetActivityResult(it)
|
||||
}
|
||||
}
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
autoCredentialViewModel.generateChallengeFailedFlow.collect {
|
||||
Log.d(TAG, "generateChallengeFailedFlow($it)")
|
||||
onSetActivityResult(ActivityResult(RESULT_CANCELED, null))
|
||||
}
|
||||
}
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorDialogViewModel.newDialogFlow.collect {
|
||||
Log.d(TAG, "newErrorDialogFlow($it)")
|
||||
@@ -236,8 +241,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorDialogViewModel.setResultFlow.collect {
|
||||
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) {
|
||||
val challengeExtras: Bundle? = autoCredentialViewModel.createGeneratingChallengeExtras()
|
||||
val overrideResult: ActivityResult = viewModel.getOverrideActivityResult(
|
||||
@@ -428,8 +427,8 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
}
|
||||
|
||||
private fun checkCredential() {
|
||||
when (autoCredentialViewModel.checkCredential()) {
|
||||
CREDENTIAL_FAIL_NEED_TO_CHOOSE_LOCK -> {
|
||||
when (autoCredentialViewModel.checkCredential(lifecycleScope)) {
|
||||
CredentialAction.FAIL_NEED_TO_CHOOSE_LOCK -> {
|
||||
val intent: Intent = autoCredentialViewModel.createChooseLockIntent(
|
||||
this,
|
||||
viewModel.request.isSuw,
|
||||
@@ -442,7 +441,7 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
CREDENTIAL_FAIL_NEED_TO_CONFIRM_LOCK -> {
|
||||
CredentialAction.FAIL_NEED_TO_CONFIRM_LOCK -> {
|
||||
val launched: Boolean = autoCredentialViewModel.createConfirmLockLauncher(
|
||||
this,
|
||||
LAUNCH_CONFIRM_LOCK_ACTIVITY,
|
||||
@@ -459,21 +458,24 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
CREDENTIAL_VALID,
|
||||
CREDENTIAL_IS_GENERATING_CHALLENGE -> {}
|
||||
CredentialAction.CREDENTIAL_VALID,
|
||||
CredentialAction.IS_GENERATING_CHALLENGE -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onChooseOrConfirmLockResult(isChooseLock: Boolean, activityResult: ActivityResult) {
|
||||
private fun onChooseOrConfirmLockResult(
|
||||
isChooseLock: Boolean,
|
||||
activityResult: ActivityResult
|
||||
) {
|
||||
if (!viewModel.isWaitingActivityResult.compareAndSet(true, false)) {
|
||||
Log.w(TAG, "isChooseLock:$isChooseLock, fail to unset waiting flag")
|
||||
}
|
||||
if (autoCredentialViewModel.checkNewCredentialFromActivityResult(
|
||||
isChooseLock, activityResult
|
||||
if (!autoCredentialViewModel.generateChallengeAsCredentialActivityResult(
|
||||
isChooseLock,
|
||||
activityResult,
|
||||
lifecycleScope
|
||||
)
|
||||
) {
|
||||
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out)
|
||||
} else {
|
||||
onSetActivityResult(activityResult)
|
||||
}
|
||||
}
|
||||
@@ -573,7 +575,11 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
viewModel.checkFinishActivityDuringOnPause(isFinishing, isChangingConfigurations)
|
||||
viewModel.checkFinishActivityDuringOnPause(
|
||||
isFinishing,
|
||||
isChangingConfigurations,
|
||||
lifecycleScope
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -596,17 +602,14 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
}
|
||||
|
||||
override val defaultViewModelCreationExtras: CreationExtras
|
||||
get() {
|
||||
val fingerprintRepository = featureFactory.biometricsRepositoryProvider
|
||||
.getFingerprintRepository(application)!!
|
||||
val credentialModel = CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
|
||||
|
||||
return MutableCreationExtras(super.defaultViewModelCreationExtras).also {
|
||||
it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(fingerprintRepository)
|
||||
it[ENROLLMENT_REQUEST_KEY] =
|
||||
EnrollmentRequest(intent, applicationContext, this is SetupActivity)
|
||||
it[USER_ID_KEY] = credentialModel.userId
|
||||
}
|
||||
get() = MutableCreationExtras(super.defaultViewModelCreationExtras).also {
|
||||
it[CHALLENGE_GENERATOR_KEY] = FingerprintChallengeGenerator(
|
||||
featureFactory.biometricsRepositoryProvider.getFingerprintRepository(application)!!
|
||||
)
|
||||
it[ENROLLMENT_REQUEST_KEY] =
|
||||
EnrollmentRequest(intent, applicationContext, this is SetupActivity)
|
||||
it[CREDENTIAL_MODEL_KEY] =
|
||||
CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock())
|
||||
}
|
||||
|
||||
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
|
||||
@@ -630,11 +633,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
autoCredentialViewModel.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEBUG = false
|
||||
private const val TAG = "FingerprintEnrollmentActivity"
|
||||
|
@@ -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 androidx.activity.result.ActivityResult
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.android.settings.biometrics.BiometricEnrollBase
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFinish.FINGERPRINT_SUGGESTION_ACTIVITY
|
||||
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
|
||||
@@ -32,6 +30,11 @@ import com.android.settings.biometrics2.data.repository.FingerprintRepository
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentRequest
|
||||
import kotlinx.atomicfu.AtomicBoolean
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Fingerprint enrollment view model implementation
|
||||
@@ -44,9 +47,9 @@ class FingerprintEnrollmentViewModel(
|
||||
|
||||
val isWaitingActivityResult: AtomicBoolean = atomic(false)
|
||||
|
||||
private val _setResultLiveData = MutableLiveData<ActivityResult>()
|
||||
val setResultLiveData: LiveData<ActivityResult>
|
||||
get() = _setResultLiveData
|
||||
private val _setResultFlow = MutableSharedFlow<ActivityResult>()
|
||||
val setResultFlow: SharedFlow<ActivityResult>
|
||||
get() = _setResultFlow.asSharedFlow()
|
||||
|
||||
var isNewFingerprintAdded = false
|
||||
set(value) {
|
||||
@@ -94,16 +97,17 @@ class FingerprintEnrollmentViewModel(
|
||||
*/
|
||||
fun checkFinishActivityDuringOnPause(
|
||||
isActivityFinishing: Boolean,
|
||||
isChangingConfigurations: Boolean
|
||||
isChangingConfigurations: Boolean,
|
||||
scope: CoroutineScope
|
||||
) {
|
||||
if (isChangingConfigurations || isActivityFinishing || request.isSuw
|
||||
|| isWaitingActivityResult.value
|
||||
) {
|
||||
return
|
||||
}
|
||||
_setResultLiveData.postValue(
|
||||
ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null)
|
||||
)
|
||||
scope.launch {
|
||||
_setResultFlow.emit(ActivityResult(BiometricEnrollBase.RESULT_TIMEOUT, null))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,23 +137,23 @@ class FingerprintEnrollmentViewModel(
|
||||
* Update FINGERPRINT_SUGGESTION_ACTIVITY into package manager
|
||||
*/
|
||||
fun updateFingerprintSuggestionEnableState(userId: Int) {
|
||||
val enrolled = fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId)
|
||||
// Only show "Add another fingerprint" if the user already enrolled one.
|
||||
// "Add fingerprint" will be shown in the main flow if the user hasn't enrolled any
|
||||
// fingerprints. If the user already added more than one fingerprint, they already know
|
||||
// to add multiple fingerprints so we don't show the suggestion.
|
||||
val state = if (fingerprintRepository.getNumOfEnrolledFingerprintsSize(userId) == 1)
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
else
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
getApplication<Application>().packageManager.setComponentEnabledSetting(
|
||||
ComponentName(
|
||||
getApplication(),
|
||||
FINGERPRINT_SUGGESTION_ACTIVITY
|
||||
),
|
||||
if (enrolled == 1)
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
else
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
state,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state = ${enrolled == 1}")
|
||||
Log.d(TAG, "$FINGERPRINT_SUGGESTION_ACTIVITY enabled state: $state")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
Reference in New Issue
Block a user