Loading src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java +20 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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), Loading @@ -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); Loading @@ -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); Loading src/com/android/settings/biometrics2/ui/model/CredentialModel.kt +0 −14 Original line number Diff line number Diff line Loading @@ -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 Loading src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt +44 −46 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading Loading @@ -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)") Loading @@ -236,8 +241,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() { ) } } } lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { errorDialogViewModel.setResultFlow.collect { Log.d(TAG, "errorDialogSetResultFlow($it)") Loading Loading @@ -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( Loading @@ -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, Loading @@ -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, Loading @@ -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) } } Loading Loading @@ -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() { Loading @@ -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) 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[USER_ID_KEY] = credentialModel.userId } it[CREDENTIAL_MODEL_KEY] = CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock()) } override val defaultViewModelProviderFactory: ViewModelProvider.Factory Loading @@ -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" Loading src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.javadeleted 100644 → 0 +0 −393 File deleted.Preview size limit exceeded, changes collapsed. Show changes src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.kt 0 → 100644 +300 −0 Original line number Diff line number Diff line /* * 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 } Loading
src/com/android/settings/biometrics2/factory/BiometricsViewModelFactory.java +20 −18 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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), Loading @@ -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); Loading @@ -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); Loading
src/com/android/settings/biometrics2/ui/model/CredentialModel.kt +0 −14 Original line number Diff line number Diff line Loading @@ -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 Loading
src/com/android/settings/biometrics2/ui/view/FingerprintEnrollmentActivity.kt +44 −46 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading Loading @@ -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)") Loading @@ -236,8 +241,6 @@ open class FingerprintEnrollmentActivity : FragmentActivity() { ) } } } lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { errorDialogViewModel.setResultFlow.collect { Log.d(TAG, "errorDialogSetResultFlow($it)") Loading Loading @@ -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( Loading @@ -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, Loading @@ -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, Loading @@ -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) } } Loading Loading @@ -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() { Loading @@ -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) 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[USER_ID_KEY] = credentialModel.userId } it[CREDENTIAL_MODEL_KEY] = CredentialModel(intent.extras, SystemClock.elapsedRealtimeClock()) } override val defaultViewModelProviderFactory: ViewModelProvider.Factory Loading @@ -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" Loading
src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.javadeleted 100644 → 0 +0 −393 File deleted.Preview size limit exceeded, changes collapsed. Show changes
src/com/android/settings/biometrics2/ui/viewmodel/AutoCredentialViewModel.kt 0 → 100644 +300 −0 Original line number Diff line number Diff line /* * 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 }