/* * 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.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.NonNull; 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, support fingerprint enrollment only and skip other biometric // enrollment methods like face unlock. public static final String EXTRA_FINGERPRINT_ENROLLMENT_ONLY = "fingerprint_enrollment_only"; // 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) && !(intent.getBooleanExtra(EXTRA_FINGERPRINT_ENROLLMENT_ONLY, false)); // 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 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.getFeatureFactory() .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 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( com.google.android.setupdesign.R.anim.sud_slide_next_in, com.google.android.setupdesign.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; } }