This reverts commit cf0501e4d7
.
Reason for revert: b/317462033, it seems a flaky but revert it first.
Change-Id: Ie1d5e279cca6477fc17d8c27c1ecda8d7a6b2553
722 lines
34 KiB
Java
722 lines
34 KiB
Java
/*
|
|
* 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.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<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(
|
|
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;
|
|
}
|
|
}
|