| /* |
| * Copyright (C) 2018 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.biometrics; |
| |
| import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL; |
| import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED; |
| |
| import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED; |
| import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED; |
| |
| import static com.google.android.setupdesign.transition.TransitionHelper.TRANSITION_FADE_THROUGH; |
| |
| import android.annotation.NonNull; |
| import android.app.Activity; |
| import android.app.admin.DevicePolicyManager; |
| import android.app.settings.SettingsEnums; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.hardware.biometrics.BiometricAuthenticator; |
| import android.hardware.biometrics.BiometricManager; |
| import android.hardware.biometrics.BiometricManager.Authenticators; |
| import android.hardware.biometrics.BiometricManager.BiometricError; |
| import android.hardware.biometrics.SensorProperties; |
| import android.hardware.face.FaceManager; |
| import android.hardware.face.FaceSensorPropertiesInternal; |
| import android.hardware.fingerprint.FingerprintManager; |
| import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; |
| import android.os.Bundle; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.Log; |
| |
| import androidx.annotation.Nullable; |
| |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.settings.R; |
| import com.android.settings.SetupWizardUtils; |
| import com.android.settings.core.InstrumentedActivity; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settings.password.ChooseLockGeneric; |
| import com.android.settings.password.ChooseLockPattern; |
| import com.android.settings.password.ChooseLockSettingsHelper; |
| |
| import com.google.android.setupcompat.util.WizardManagerHelper; |
| import com.google.android.setupdesign.transition.TransitionHelper; |
| |
| import java.util.List; |
| |
| /** |
| * Trampoline activity launched by the {@code android.settings.BIOMETRIC_ENROLL} action which |
| * shows the user an appropriate enrollment flow depending on the device's biometric hardware. |
| * This activity must only allow enrollment of biometrics that can be used by |
| * {@link android.hardware.biometrics.BiometricPrompt}. |
| */ |
| public class BiometricEnrollActivity extends InstrumentedActivity { |
| |
| private static final String TAG = "BiometricEnrollActivity"; |
| |
| private static final int REQUEST_CHOOSE_LOCK = 1; |
| private static final int REQUEST_CONFIRM_LOCK = 2; |
| // prompt for parental consent options |
| private static final int REQUEST_CHOOSE_OPTIONS = 3; |
| // prompt hand phone back to parent after enrollment |
| private static final int REQUEST_HANDOFF_PARENT = 4; |
| private static final int REQUEST_SINGLE_ENROLL_FINGERPRINT = 5; |
| private static final int REQUEST_SINGLE_ENROLL_FACE = 6; |
| |
| public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; |
| |
| // Intent extra. If true, biometric enrollment should skip introductory screens. Currently |
| // this only applies to fingerprint. |
| public static final String EXTRA_SKIP_INTRO = "skip_intro"; |
| |
| // Intent extra. If true, parental consent will be requested before user enrollment. |
| public static final String EXTRA_REQUIRE_PARENTAL_CONSENT = "require_consent"; |
| |
| // Intent extra. If true, the screen asking the user to return the device to their parent will |
| // be skipped after enrollment. |
| public static final String EXTRA_SKIP_RETURN_TO_PARENT = "skip_return_to_parent"; |
| |
| // If EXTRA_REQUIRE_PARENTAL_CONSENT was used to start the activity then the result |
| // intent will include this extra containing a bundle of the form: |
| // "modality" -> consented (boolean). |
| public static final String EXTRA_PARENTAL_CONSENT_STATUS = "consent_status"; |
| |
| private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials"; |
| private static final String SAVED_STATE_IS_SINGLE_ENROLLING = |
| "is_single_enrolling"; |
| private static final String SAVED_STATE_PASS_THROUGH_EXTRAS_FROM_CHOSEN_LOCK_IN_SUW = |
| "pass_through_extras_from_chosen_lock_in_suw"; |
| private static final String SAVED_STATE_ENROLL_ACTION_LOGGED = "enroll_action_logged"; |
| private static final String SAVED_STATE_PARENTAL_OPTIONS = "enroll_preferences"; |
| private static final String SAVED_STATE_GK_PW_HANDLE = "gk_pw_handle"; |
| |
| public static final class InternalActivity extends BiometricEnrollActivity {} |
| |
| private int mUserId = UserHandle.myUserId(); |
| private boolean mConfirmingCredentials; |
| private boolean mIsSingleEnrolling; |
| private Bundle mPassThroughExtrasFromChosenLockInSuw = null; |
| private boolean mIsEnrollActionLogged; |
| private boolean mHasFeatureFace = false; |
| private boolean mHasFeatureFingerprint = false; |
| private boolean mIsFaceEnrollable = false; |
| private boolean mIsFingerprintEnrollable = false; |
| private boolean mParentalOptionsRequired = false; |
| private boolean mSkipReturnToParent = false; |
| private Bundle mParentalOptions; |
| @Nullable private Long mGkPwHandle; |
| @Nullable private ParentalConsentHelper mParentalConsentHelper; |
| |
| @Override |
| public void onCreate(@Nullable Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| final Intent intent = getIntent(); |
| |
| if (this instanceof InternalActivity) { |
| mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); |
| if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { |
| mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent()); |
| } |
| } else if (WizardManagerHelper.isAnySetupWizard(getIntent())) { |
| if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { |
| mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(getIntent()); |
| } |
| |
| } |
| |
| if (savedInstanceState != null) { |
| mConfirmingCredentials = savedInstanceState.getBoolean( |
| SAVED_STATE_CONFIRMING_CREDENTIALS, false); |
| mIsSingleEnrolling = savedInstanceState.getBoolean( |
| SAVED_STATE_IS_SINGLE_ENROLLING, false); |
| mPassThroughExtrasFromChosenLockInSuw = savedInstanceState.getBundle( |
| SAVED_STATE_PASS_THROUGH_EXTRAS_FROM_CHOSEN_LOCK_IN_SUW); |
| mIsEnrollActionLogged = savedInstanceState.getBoolean( |
| SAVED_STATE_ENROLL_ACTION_LOGGED, false); |
| mParentalOptions = savedInstanceState.getBundle(SAVED_STATE_PARENTAL_OPTIONS); |
| if (savedInstanceState.containsKey(SAVED_STATE_GK_PW_HANDLE)) { |
| mGkPwHandle = savedInstanceState.getLong(SAVED_STATE_GK_PW_HANDLE); |
| } |
| } |
| |
| // Log a framework stats event if this activity was launched via intent action. |
| if (!mIsEnrollActionLogged && ACTION_BIOMETRIC_ENROLL.equals(intent.getAction())) { |
| mIsEnrollActionLogged = true; |
| |
| // Get the current status for each authenticator type. |
| @BiometricError final int strongBiometricStatus; |
| @BiometricError final int weakBiometricStatus; |
| @BiometricError final int deviceCredentialStatus; |
| final BiometricManager bm = getSystemService(BiometricManager.class); |
| if (bm != null) { |
| strongBiometricStatus = bm.canAuthenticate(Authenticators.BIOMETRIC_STRONG); |
| weakBiometricStatus = bm.canAuthenticate(Authenticators.BIOMETRIC_WEAK); |
| deviceCredentialStatus = bm.canAuthenticate(Authenticators.DEVICE_CREDENTIAL); |
| } else { |
| strongBiometricStatus = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; |
| weakBiometricStatus = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; |
| deviceCredentialStatus = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; |
| } |
| |
| FrameworkStatsLog.write(FrameworkStatsLog.AUTH_ENROLL_ACTION_INVOKED, |
| strongBiometricStatus == BiometricManager.BIOMETRIC_SUCCESS, |
| weakBiometricStatus == BiometricManager.BIOMETRIC_SUCCESS, |
| deviceCredentialStatus == BiometricManager.BIOMETRIC_SUCCESS, |
| intent.hasExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED), |
| intent.getIntExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, 0)); |
| } |
| |
| // Put the theme in the intent so it gets propagated to other activities in the flow |
| if (intent.getStringExtra(WizardManagerHelper.EXTRA_THEME) == null) { |
| intent.putExtra( |
| WizardManagerHelper.EXTRA_THEME, |
| SetupWizardUtils.getThemeString(intent)); |
| } |
| |
| final PackageManager pm = getApplicationContext().getPackageManager(); |
| mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); |
| mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); |
| |
| // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. |
| final int authenticators = getIntent().getIntExtra( |
| EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); |
| Log.d(TAG, "Authenticators: " + BiometricManager.authenticatorToStr(authenticators)); |
| |
| mParentalOptionsRequired = intent.getBooleanExtra(EXTRA_REQUIRE_PARENTAL_CONSENT, false); |
| mSkipReturnToParent = intent.getBooleanExtra(EXTRA_SKIP_RETURN_TO_PARENT, false); |
| |
| // determine what can be enrolled |
| final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); |
| final boolean isMultiSensor = mHasFeatureFace && mHasFeatureFingerprint; |
| |
| Log.d(TAG, "parentalOptionsRequired: " + mParentalOptionsRequired |
| + ", skipReturnToParent: " + mSkipReturnToParent |
| + ", isSetupWizard: " + isSetupWizard |
| + ", isMultiSensor: " + isMultiSensor); |
| |
| if (mHasFeatureFace) { |
| final FaceManager faceManager = getSystemService(FaceManager.class); |
| final List<FaceSensorPropertiesInternal> faceProperties = |
| faceManager.getSensorPropertiesInternal(); |
| final int maxFacesEnrollableIfSUW = getApplicationContext().getResources() |
| .getInteger(R.integer.suw_max_faces_enrollable); |
| if (!faceProperties.isEmpty()) { |
| final FaceSensorPropertiesInternal props = faceProperties.get(0); |
| final int maxEnrolls = |
| isSetupWizard ? maxFacesEnrollableIfSUW : props.maxEnrollmentsPerUser; |
| final boolean isFaceStrong = |
| props.sensorStrength == SensorProperties.STRENGTH_STRONG; |
| mIsFaceEnrollable = |
| faceManager.getEnrolledFaces(mUserId).size() < maxEnrolls; |
| |
| // If we expect strong bio only, check if face is strong |
| if (authenticators == Authenticators.BIOMETRIC_STRONG && !isFaceStrong) { |
| mIsFaceEnrollable = false; |
| } |
| |
| final boolean parentalConsent = isSetupWizard || (mParentalOptionsRequired |
| && !WizardManagerHelper.isUserSetupComplete(this)); |
| if (parentalConsent && isMultiSensor && mIsFaceEnrollable) { |
| // Exclude face enrollment from setup wizard if feature config not supported |
| // in setup wizard flow, we still allow user enroll faces through settings. |
| mIsFaceEnrollable = FeatureFactory.getFactory(getApplicationContext()) |
| .getFaceFeatureProvider() |
| .isSetupWizardSupported(getApplicationContext()); |
| Log.d(TAG, "config_suw_support_face_enroll: " + mIsFaceEnrollable); |
| } |
| } |
| } |
| updateFingerprintEnrollable(isSetupWizard); |
| |
| // TODO(b/195128094): remove this restriction |
| // Consent can only be recorded when this activity is launched directly from the kids |
| // module. This can be removed when there is a way to notify consent status out of band. |
| if (isSetupWizard && mParentalOptionsRequired) { |
| Log.w(TAG, "Enrollment with parental consent is not supported when launched " |
| + " directly from SuW - skipping enrollment"); |
| setResult(RESULT_SKIP); |
| finish(); |
| return; |
| } |
| |
| // Only allow the consent flow to happen once when running from setup wizard. |
| // This isn't common and should only happen if setup wizard is not completed normally |
| // due to a restart, etc. |
| // This check should probably remain even if b/195128094 is fixed to prevent SuW from |
| // restarting the process once it has been fully completed at least one time. |
| if (isSetupWizard && mParentalOptionsRequired) { |
| final boolean consentAlreadyManaged = ParentalControlsUtils.parentConsentRequired(this, |
| BiometricAuthenticator.TYPE_FACE | BiometricAuthenticator.TYPE_FINGERPRINT) |
| != null; |
| if (consentAlreadyManaged) { |
| Log.w(TAG, "Consent was already setup - skipping enrollment"); |
| setResult(RESULT_SKIP); |
| finish(); |
| return; |
| } |
| } |
| |
| if (mParentalOptionsRequired && mParentalOptions == null) { |
| mParentalConsentHelper = new ParentalConsentHelper(mGkPwHandle); |
| setOrConfirmCredentialsNow(); |
| } else { |
| // Start enrollment process if we haven't bailed out yet |
| startEnrollWith(authenticators, isSetupWizard); |
| } |
| } |
| |
| private void updateFingerprintEnrollable(boolean isSetupWizard) { |
| if (mHasFeatureFingerprint) { |
| final int authenticators = getIntent().getIntExtra( |
| EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); |
| |
| final FingerprintManager fpManager = getSystemService(FingerprintManager.class); |
| final List<FingerprintSensorPropertiesInternal> fpProperties = |
| fpManager.getSensorPropertiesInternal(); |
| final int maxFingerprintsEnrollableIfSUW = getApplicationContext().getResources() |
| .getInteger(R.integer.suw_max_fingerprints_enrollable); |
| if (!fpProperties.isEmpty()) { |
| final int maxEnrolls = |
| isSetupWizard ? maxFingerprintsEnrollableIfSUW |
| : fpProperties.get(0).maxEnrollmentsPerUser; |
| final boolean isFingerprintStrong = |
| fpProperties.get(0).sensorStrength == SensorProperties.STRENGTH_STRONG; |
| mIsFingerprintEnrollable = |
| fpManager.getEnrolledFingerprints(mUserId).size() < maxEnrolls; |
| |
| // If we expect strong bio only, check if fingerprint is strong |
| if (authenticators == Authenticators.BIOMETRIC_STRONG && !isFingerprintStrong) { |
| mIsFingerprintEnrollable = false; |
| } |
| } |
| } |
| } |
| |
| private void startEnrollWith(@Authenticators.Types int authenticators, boolean setupWizard) { |
| // If the caller is not setup wizard, and the user has something enrolled, finish. |
| // Allow parental consent flow to skip this check, since one modality could be consented |
| // and another non-consented. This can also happen if the restriction is applied when |
| // enrollments already exists. |
| if (!setupWizard && !mParentalOptionsRequired) { |
| final BiometricManager bm = getSystemService(BiometricManager.class); |
| final @BiometricError int result = bm.canAuthenticate(authenticators); |
| if (result != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { |
| Log.e(TAG, "Unexpected result (has enrollments): " + result); |
| finish(); |
| return; |
| } |
| } |
| |
| boolean canUseFace = mHasFeatureFace; |
| boolean canUseFingerprint = mHasFeatureFingerprint; |
| if (mParentalOptionsRequired) { |
| if (mParentalOptions == null) { |
| throw new IllegalStateException("consent options required, but not set"); |
| } |
| canUseFace = canUseFace |
| && ParentalConsentHelper.hasFaceConsent(mParentalOptions); |
| canUseFingerprint = canUseFingerprint |
| && ParentalConsentHelper.hasFingerprintConsent(mParentalOptions); |
| } |
| |
| // This will need to be updated if the device has sensors other than BIOMETRIC_STRONG |
| if (!setupWizard && authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { |
| launchCredentialOnlyEnroll(); |
| finish(); |
| } else if (canUseFace || canUseFingerprint) { |
| if (mGkPwHandle == null) { |
| setOrConfirmCredentialsNow(); |
| } else if (canUseFingerprint && mIsFingerprintEnrollable) { |
| launchFingerprintOnlyEnroll(); |
| } else if (canUseFace && mIsFaceEnrollable) { |
| launchFaceOnlyEnroll(); |
| } else { |
| setOrConfirmCredentialsNow(); |
| } |
| } else { // no modalities available |
| if (mParentalOptionsRequired) { |
| Log.d(TAG, "No consent for any modality: skipping enrollment"); |
| setResult(RESULT_OK, newResultIntent()); |
| } else { |
| Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")"); |
| } |
| finish(); |
| } |
| } |
| |
| @Override |
| protected void onSaveInstanceState(@NonNull Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials); |
| outState.putBoolean(SAVED_STATE_IS_SINGLE_ENROLLING, mIsSingleEnrolling); |
| outState.putBundle(SAVED_STATE_PASS_THROUGH_EXTRAS_FROM_CHOSEN_LOCK_IN_SUW, |
| mPassThroughExtrasFromChosenLockInSuw); |
| outState.putBoolean(SAVED_STATE_ENROLL_ACTION_LOGGED, mIsEnrollActionLogged); |
| if (mParentalOptions != null) { |
| outState.putBundle(SAVED_STATE_PARENTAL_OPTIONS, mParentalOptions); |
| } |
| if (mGkPwHandle != null) { |
| outState.putLong(SAVED_STATE_GK_PW_HANDLE, mGkPwHandle); |
| } |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| super.onActivityResult(requestCode, resultCode, data); |
| |
| if (isSuccessfulChooseCredential(requestCode, resultCode) |
| && data != null && data.getExtras() != null && data.getExtras().size() > 0 |
| && WizardManagerHelper.isAnySetupWizard(getIntent())) { |
| mPassThroughExtrasFromChosenLockInSuw = data.getExtras(); |
| } |
| |
| Log.d(TAG, |
| "onActivityResult(requestCode=" + requestCode + ", resultCode=" + resultCode + ")"); |
| // single enrollment is handled entirely by the launched activity |
| // this handles multi enroll or if parental consent is required |
| if (mParentalConsentHelper != null) { |
| // Lazily retrieve the values from ParentalControlUtils, since the value may not be |
| // ready in onCreate. |
| final boolean faceConsentRequired = ParentalControlsUtils |
| .parentConsentRequired(this, BiometricAuthenticator.TYPE_FACE) != null; |
| final boolean fpConsentRequired = ParentalControlsUtils |
| .parentConsentRequired(this, BiometricAuthenticator.TYPE_FINGERPRINT) != null; |
| |
| final boolean requestFaceConsent = faceConsentRequired |
| && mHasFeatureFace |
| && mIsFaceEnrollable; |
| final boolean requestFpConsent = fpConsentRequired && mHasFeatureFingerprint; |
| |
| Log.d(TAG, "faceConsentRequired: " + faceConsentRequired |
| + ", fpConsentRequired: " + fpConsentRequired |
| + ", hasFeatureFace: " + mHasFeatureFace |
| + ", hasFeatureFingerprint: " + mHasFeatureFingerprint |
| + ", faceEnrollable: " + mIsFaceEnrollable |
| + ", fpEnrollable: " + mIsFingerprintEnrollable); |
| |
| mParentalConsentHelper.setConsentRequirement(requestFaceConsent, requestFpConsent); |
| |
| handleOnActivityResultWhileConsenting(requestCode, resultCode, data); |
| } else { |
| handleOnActivityResultWhileEnrolling(requestCode, resultCode, data); |
| } |
| } |
| |
| // handles responses while parental consent is pending |
| private void handleOnActivityResultWhileConsenting( |
| int requestCode, int resultCode, Intent data) { |
| overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); |
| |
| switch (requestCode) { |
| case REQUEST_CHOOSE_LOCK: |
| case REQUEST_CONFIRM_LOCK: |
| mConfirmingCredentials = false; |
| if (isSuccessfulConfirmOrChooseCredential(requestCode, resultCode)) { |
| updateGatekeeperPasswordHandle(data); |
| if (!mParentalConsentHelper.launchNext(this, REQUEST_CHOOSE_OPTIONS)) { |
| Log.e(TAG, "Nothing to prompt for consent (no modalities enabled)!"); |
| finish(); |
| } |
| } else { |
| Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); |
| setResult(resultCode); |
| finish(); |
| } |
| break; |
| case REQUEST_CHOOSE_OPTIONS: |
| if (resultCode == RESULT_CONSENT_GRANTED || resultCode == RESULT_CONSENT_DENIED) { |
| final boolean isStillPrompting = mParentalConsentHelper.launchNext( |
| this, REQUEST_CHOOSE_OPTIONS, resultCode, data); |
| if (!isStillPrompting) { |
| mParentalOptions = mParentalConsentHelper.getConsentResult(); |
| mParentalConsentHelper = null; |
| Log.d(TAG, "Enrollment consent options set, starting enrollment: " |
| + mParentalOptions); |
| // Note that we start enrollment with CONVENIENCE instead of the default |
| // of WEAK in startEnroll(), since we want to allow enrollment for any |
| // sensor as long as it has been consented for. We should eventually |
| // clean up this logic and do something like pass in the parental consent |
| // result, so that we can request enrollment for specific sensors, but |
| // that's quite a large and risky change to the startEnrollWith() logic. |
| startEnrollWith(Authenticators.BIOMETRIC_CONVENIENCE, |
| WizardManagerHelper.isAnySetupWizard(getIntent())); |
| } |
| } else { |
| Log.d(TAG, "Unknown or cancelled parental consent"); |
| setResult(RESULT_CANCELED, newResultIntent()); |
| finish(); |
| } |
| break; |
| default: |
| Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing"); |
| finish(); |
| } |
| } |
| |
| // handles responses while multi biometric enrollment is pending |
| private void handleOnActivityResultWhileEnrolling( |
| int requestCode, int resultCode, Intent data) { |
| |
| Log.d(TAG, "handleOnActivityResultWhileEnrolling, request = " + requestCode + "" |
| + ", resultCode = " + resultCode); |
| switch (requestCode) { |
| case REQUEST_HANDOFF_PARENT: |
| setResult(RESULT_OK, newResultIntent()); |
| finish(); |
| break; |
| case REQUEST_CHOOSE_LOCK: |
| case REQUEST_CONFIRM_LOCK: |
| mConfirmingCredentials = false; |
| final boolean isOk = |
| isSuccessfulConfirmOrChooseCredential(requestCode, resultCode); |
| if (isOk && (mIsFaceEnrollable || mIsFingerprintEnrollable)) { |
| // Apply forward animation during the transition from ChooseLock/ConfirmLock to |
| // SetupFingerprintEnrollIntroduction/FingerprintEnrollmentActivity |
| TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH); |
| updateGatekeeperPasswordHandle(data); |
| if (mIsFingerprintEnrollable) { |
| launchFingerprintOnlyEnroll(); |
| } else { |
| launchFaceOnlyEnroll(); |
| } |
| } else { |
| Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); |
| setResult(resultCode, newResultIntent()); |
| finish(); |
| } |
| break; |
| case REQUEST_SINGLE_ENROLL_FINGERPRINT: |
| mIsSingleEnrolling = false; |
| if (resultCode == BiometricEnrollBase.RESULT_FINISHED) { |
| // FingerprintEnrollIntroduction's visibility is determined by |
| // mIsFingerprintEnrollable. Keep this value up-to-date after a successful |
| // enrollment. |
| updateFingerprintEnrollable(WizardManagerHelper.isAnySetupWizard(getIntent())); |
| } |
| if ((resultCode == BiometricEnrollBase.RESULT_SKIP |
| || resultCode == BiometricEnrollBase.RESULT_FINISHED) |
| && mIsFaceEnrollable) { |
| // Apply forward animation during the transition from |
| // SetupFingerprintEnroll*/FingerprintEnrollmentActivity to |
| // SetupFaceEnrollIntroduction |
| TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH); |
| launchFaceOnlyEnroll(); |
| } else { |
| finishOrLaunchHandToParent(resultCode); |
| } |
| break; |
| case REQUEST_SINGLE_ENROLL_FACE: |
| mIsSingleEnrolling = false; |
| if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable) { |
| launchFingerprintOnlyEnroll(); |
| } else { |
| finishOrLaunchHandToParent(resultCode); |
| } |
| break; |
| default: |
| Log.w(TAG, "Unknown enrolling requestCode: " + requestCode + ", finishing"); |
| finish(); |
| } |
| } |
| |
| @Override |
| public void finish() { |
| if (mGkPwHandle != null) { |
| // When launched as InternalActivity, the mGkPwHandle was gotten from intent extra |
| // instead of requesting from the user. Do not remove the mGkPwHandle in service side |
| // for this case because the caller activity may still need it and will be responsible |
| // for removing it. |
| if (!(this instanceof InternalActivity)) { |
| BiometricUtils.removeGatekeeperPasswordHandle(this, mGkPwHandle); |
| } |
| } |
| super.finish(); |
| } |
| |
| private void finishOrLaunchHandToParent(int resultCode) { |
| if (mParentalOptionsRequired) { |
| if (!mSkipReturnToParent) { |
| launchHandoffToParent(); |
| } else { |
| setResult(RESULT_OK, newResultIntent()); |
| finish(); |
| } |
| } else { |
| setResult(resultCode, newResultIntent()); |
| finish(); |
| } |
| } |
| |
| @NonNull |
| private Intent newResultIntent() { |
| final Intent intent = new Intent(); |
| if (mParentalOptionsRequired && mParentalOptions != null) { |
| final Bundle consentStatus = mParentalOptions.deepCopy(); |
| intent.putExtra(EXTRA_PARENTAL_CONSENT_STATUS, consentStatus); |
| Log.v(TAG, "Result consent status: " + consentStatus); |
| } |
| if (mPassThroughExtrasFromChosenLockInSuw != null) { |
| intent.putExtras(mPassThroughExtrasFromChosenLockInSuw); |
| } |
| return intent; |
| } |
| |
| private static boolean isSuccessfulConfirmOrChooseCredential(int requestCode, int resultCode) { |
| return isSuccessfulChooseCredential(requestCode, resultCode) |
| || isSuccessfulConfirmCredential(requestCode, resultCode); |
| } |
| |
| private static boolean isSuccessfulChooseCredential(int requestCode, int resultCode) { |
| return requestCode == REQUEST_CHOOSE_LOCK |
| && resultCode == ChooseLockPattern.RESULT_FINISHED; |
| } |
| |
| private static boolean isSuccessfulConfirmCredential(int requestCode, int resultCode) { |
| return requestCode == REQUEST_CONFIRM_LOCK && resultCode == RESULT_OK; |
| } |
| |
| @Override |
| protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { |
| final int newResid = SetupWizardUtils.getTheme(this, getIntent()); |
| theme.applyStyle(R.style.SetupWizardPartnerResource, true); |
| super.onApplyThemeResource(theme, newResid, first); |
| } |
| |
| private void setOrConfirmCredentialsNow() { |
| if (!mConfirmingCredentials) { |
| mConfirmingCredentials = true; |
| if (!userHasPassword(mUserId)) { |
| launchChooseLock(); |
| } else { |
| launchConfirmLock(); |
| } |
| } |
| } |
| |
| private void updateGatekeeperPasswordHandle(@NonNull Intent data) { |
| mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data); |
| if (mParentalConsentHelper != null) { |
| mParentalConsentHelper.updateGatekeeperHandle(data); |
| } |
| } |
| |
| private boolean userHasPassword(int userId) { |
| final UserManager userManager = getSystemService(UserManager.class); |
| final int passwordQuality = new LockPatternUtils(this) |
| .getActivePasswordQuality(userManager.getCredentialOwnerProfile(userId)); |
| return passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; |
| } |
| |
| private void launchChooseLock() { |
| Log.d(TAG, "launchChooseLock"); |
| |
| Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent()); |
| intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); |
| if (mIsFingerprintEnrollable && mIsFaceEnrollable) { |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true); |
| } else if (mIsFaceEnrollable) { |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, true); |
| } else if (mIsFingerprintEnrollable) { |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); |
| } |
| |
| if (mUserId != UserHandle.USER_NULL) { |
| intent.putExtra(Intent.EXTRA_USER_ID, mUserId); |
| } |
| startActivityForResult(intent, REQUEST_CHOOSE_LOCK); |
| } |
| |
| private void launchConfirmLock() { |
| Log.d(TAG, "launchConfirmLock"); |
| |
| final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this); |
| builder.setRequestCode(REQUEST_CONFIRM_LOCK) |
| .setRequestGatekeeperPasswordHandle(true) |
| .setForegroundOnly(true) |
| .setReturnCredentials(true); |
| if (mUserId != UserHandle.USER_NULL) { |
| builder.setUserId(mUserId); |
| } |
| final boolean launched = builder.show(); |
| if (!launched) { |
| // This shouldn't happen, as we should only end up at this step if a lock thingy is |
| // already set. |
| finish(); |
| } |
| } |
| |
| // This should only be used to launch enrollment for single-sensor devices. |
| private void launchSingleSensorEnrollActivity(@NonNull Intent intent, int requestCode) { |
| byte[] hardwareAuthToken = null; |
| if (this instanceof InternalActivity) { |
| hardwareAuthToken = getIntent().getByteArrayExtra( |
| ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); |
| } |
| BiometricUtils.launchEnrollForResult(this, intent, requestCode, hardwareAuthToken, |
| mGkPwHandle, mUserId); |
| } |
| |
| private void launchCredentialOnlyEnroll() { |
| final Intent intent; |
| // If only device credential was specified, ask the user to only set that up. |
| intent = new Intent(this, ChooseLockGeneric.class); |
| intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); |
| launchSingleSensorEnrollActivity(intent, 0 /* requestCode */); |
| } |
| |
| private void launchFingerprintOnlyEnroll() { |
| if (!mIsSingleEnrolling) { |
| mIsSingleEnrolling = true; |
| final Intent intent; |
| // ChooseLockGeneric can request to start fingerprint enroll bypassing the intro screen. |
| if (getIntent().getBooleanExtra(EXTRA_SKIP_INTRO, false) |
| && this instanceof InternalActivity) { |
| intent = BiometricUtils.getFingerprintFindSensorIntent(this, getIntent()); |
| } else { |
| intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent()); |
| } |
| launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL_FINGERPRINT); |
| } |
| } |
| |
| private void launchFaceOnlyEnroll() { |
| if (!mIsSingleEnrolling) { |
| mIsSingleEnrolling = true; |
| final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent()); |
| launchSingleSensorEnrollActivity(intent, REQUEST_SINGLE_ENROLL_FACE); |
| } |
| } |
| |
| private void launchHandoffToParent() { |
| final Intent intent = BiometricUtils.getHandoffToParentIntent(this, getIntent()); |
| startActivityForResult(intent, REQUEST_HANDOFF_PARENT); |
| } |
| |
| @Override |
| public int getMetricsCategory() { |
| return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY; |
| } |
| } |