Merge changes from topic "unicorn-1" into sc-dev

* changes:
  Update strings for parental consent flow in setup wizard.
  Add plumbing and placeholder screens for parental consent flow.
This commit is contained in:
Joe Bolinger
2021-06-18 16:28:27 +00:00
committed by Android (Google) Code Review
14 changed files with 1096 additions and 299 deletions

View File

@@ -19,6 +19,9 @@ 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 android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
@@ -64,6 +67,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
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;
public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP;
@@ -71,8 +76,12 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// this only applies to fingerprint.
public static final String EXTRA_SKIP_INTRO = "skip_intro";
// TODO: temporary while waiting for team to add real flag
public static final String EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT = "require_consent";
private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials";
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 {}
@@ -80,9 +89,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
private int mUserId = UserHandle.myUserId();
private boolean mConfirmingCredentials;
private boolean mIsEnrollActionLogged;
private boolean mIsFaceEnrollable;
private boolean mIsFingerprintEnrollable;
private boolean mHasFeatureFace = false;
private boolean mHasFeatureFingerprint = false;
private boolean mIsFaceEnrollable = false;
private boolean mIsFingerprintEnrollable = false;
private boolean mParentalOptionsRequired = false;
private Bundle mParentalOptions;
@Nullable private Long mGkPwHandle;
@Nullable private ParentalConsentHelper mParentalConsentHelper;
@Nullable private MultiBiometricEnrollHelper mMultiBiometricEnrollHelper;
@Override
@@ -101,6 +115,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
SAVED_STATE_CONFIRMING_CREDENTIALS, false);
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);
}
@@ -141,52 +156,98 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
SetupWizardUtils.getThemeString(intent));
}
// Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL.
final int authenticators = intent.getIntExtra(
EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK);
final PackageManager pm = getApplicationContext().getPackageManager();
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
// determine what can be enrolled
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
if (mHasFeatureFace) {
final FaceManager faceManager = getSystemService(FaceManager.class);
final List<FaceSensorPropertiesInternal> faceProperties =
faceManager.getSensorPropertiesInternal();
if (!faceProperties.isEmpty()) {
final int maxEnrolls =
isSetupWizard ? 1 : faceProperties.get(0).maxEnrollmentsPerUser;
mIsFaceEnrollable =
faceManager.getEnrolledFaces(mUserId).size() < maxEnrolls;
}
}
if (mHasFeatureFingerprint) {
final FingerprintManager fpManager = getSystemService(FingerprintManager.class);
final List<FingerprintSensorPropertiesInternal> fpProperties =
fpManager.getSensorPropertiesInternal();
if (!fpProperties.isEmpty()) {
final int maxEnrolls =
isSetupWizard ? 1 : fpProperties.get(0).maxEnrollmentsPerUser;
mIsFingerprintEnrollable =
fpManager.getEnrolledFingerprints(mUserId).size() < maxEnrolls;
}
}
// TODO(b/188847063): replace with real flag when ready
mParentalOptionsRequired = intent.getBooleanExtra(
BiometricEnrollActivity.EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT, false);
if (mParentalOptionsRequired && mParentalOptions == null) {
mParentalConsentHelper = new ParentalConsentHelper(
mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
setOrConfirmCredentialsNow();
} else {
startEnroll();
}
}
private void startEnroll() {
// TODO(b/188847063): This can be deleted, but log it now until it's wired up for real.
if (mParentalOptionsRequired) {
if (mParentalOptions == null) {
throw new IllegalStateException("consent options required, but not set");
}
Log.d(TAG, "consent for face: "
+ ParentalConsentHelper.hasFaceConsent(mParentalOptions));
Log.d(TAG, "consent for fingerprint: "
+ ParentalConsentHelper.hasFingerprintConsent(mParentalOptions));
} else {
Log.d(TAG, "startEnroll without requiring consent");
}
// 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: " + authenticators);
final PackageManager pm = getApplicationContext().getPackageManager();
final boolean hasFeatureFingerprint =
pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
final boolean hasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
startEnrollWith(authenticators, WizardManagerHelper.isAnySetupWizard(getIntent()));
}
if (isSetupWizard) {
if (hasFeatureFace && hasFeatureFingerprint) {
setupForMultiBiometricEnroll();
} else if (hasFeatureFace) {
launchFaceOnlyEnroll();
} else if (hasFeatureFingerprint) {
launchFingerprintOnlyEnroll();
} else {
Log.e(TAG, "No biometrics but started by SUW?");
finish();
}
} else {
// If the caller is not setup wizard, and the user has something enrolled, finish.
private void startEnrollWith(@Authenticators.Types int authenticators, boolean setupWizard) {
// If the caller is not setup wizard, and the user has something enrolled, finish.
if (!setupWizard) {
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: " + result);
Log.e(TAG, "Unexpected result (has enrollments): " + result);
finish();
return;
}
}
// This will need to be updated if the device has sensors other than BIOMETRIC_STRONG
if (authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
launchCredentialOnlyEnroll();
} else if (hasFeatureFace && hasFeatureFingerprint) {
setupForMultiBiometricEnroll();
} else if (hasFeatureFingerprint) {
launchFingerprintOnlyEnroll();
} else if (hasFeatureFace) {
launchFaceOnlyEnroll();
// This will need to be updated if the device has sensors other than BIOMETRIC_STRONG
if (!setupWizard && authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
launchCredentialOnlyEnroll();
} else if (mHasFeatureFace && mHasFeatureFingerprint) {
if (mParentalOptionsRequired && mGkPwHandle != null) {
launchFaceAndFingerprintEnroll();
} else {
Log.e(TAG, "Unknown state, finishing");
finish();
setOrConfirmCredentialsNow();
}
} else if (mHasFeatureFingerprint) {
launchFingerprintOnlyEnroll();
} else if (mHasFeatureFace) {
launchFaceOnlyEnroll();
} else {
Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")");
finish();
}
}
@@ -195,6 +256,9 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials);
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);
}
@@ -204,31 +268,84 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// single enrollment is handled entirely by the launched activity
// this handles multi enroll or if parental consent is required
if (mParentalConsentHelper != null) {
handleOnActivityResultWhileConsenting(requestCode, resultCode, data);
} else {
handleOnActivityResultWhileEnrollingMultiple(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) {
Log.d(TAG, "Enrollment options set, starting enrollment now");
mParentalOptions = mParentalConsentHelper.getConsentResult();
mParentalConsentHelper = null;
startEnroll();
}
} else {
Log.d(TAG, "Unknown or cancelled parental consent");
setResult(RESULT_CANCELED);
finish();
}
break;
default:
Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing");
finish();
}
}
// handles responses while multi biometric enrollment is pending
private void handleOnActivityResultWhileEnrollingMultiple(
int requestCode, int resultCode, Intent data) {
if (mMultiBiometricEnrollHelper == null) {
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 (resultCode == ChooseLockPattern.RESULT_FINISHED) {
startMultiBiometricEnroll(data);
final boolean isOk =
isSuccessfulConfirmOrChooseCredential(requestCode, resultCode);
// single modality enrollment requests confirmation directly
// via BiometricEnrollBase#onCreate and should never get here
if (isOk && mHasFeatureFace && mHasFeatureFingerprint) {
updateGatekeeperPasswordHandle(data);
launchFaceAndFingerprintEnroll();
} else {
Log.d(TAG, "Unknown result for chooseLock: " + resultCode);
Log.d(TAG, "Unknown result for set/choose lock: " + resultCode);
setResult(resultCode);
finish();
}
break;
case REQUEST_CONFIRM_LOCK:
mConfirmingCredentials = false;
if (resultCode == RESULT_OK) {
startMultiBiometricEnroll(data);
} else {
Log.d(TAG, "Unknown result for confirmLock: " + resultCode);
finish();
}
break;
default:
Log.d(TAG, "Unknown requestCode: " + requestCode + ", finishing");
Log.w(TAG, "Unknown enrolling requestCode: " + requestCode + ", finishing");
finish();
}
} else {
@@ -236,18 +353,28 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
}
}
private static boolean isSuccessfulConfirmOrChooseCredential(int requestCode, int resultCode) {
final boolean okChoose = requestCode == REQUEST_CHOOSE_LOCK
&& resultCode == ChooseLockPattern.RESULT_FINISHED;
final boolean okConfirm = requestCode == REQUEST_CONFIRM_LOCK
&& resultCode == RESULT_OK;
return okChoose || okConfirm;
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
final int new_resid = SetupWizardUtils.getTheme(this, getIntent());
final int newResid = SetupWizardUtils.getTheme(this, getIntent());
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
super.onApplyThemeResource(theme, new_resid, first);
super.onApplyThemeResource(theme, newResid, first);
}
@Override
protected void onStop() {
super.onStop();
if (mConfirmingCredentials || mMultiBiometricEnrollHelper != null) {
if (mConfirmingCredentials
|| mMultiBiometricEnrollHelper != null
|| mParentalConsentHelper != null) {
return;
}
@@ -257,7 +384,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
}
}
private void setupForMultiBiometricEnroll() {
private void setOrConfirmCredentialsNow() {
if (!mConfirmingCredentials) {
mConfirmingCredentials = true;
if (!userHasPassword(mUserId)) {
@@ -268,37 +396,11 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
}
}
private void startMultiBiometricEnroll(Intent data) {
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
final FaceManager faceManager = getSystemService(FaceManager.class);
final List<FingerprintSensorPropertiesInternal> fpProperties =
fingerprintManager.getSensorPropertiesInternal();
final List<FaceSensorPropertiesInternal> faceProperties =
faceManager.getSensorPropertiesInternal();
private void updateGatekeeperPasswordHandle(@NonNull Intent data) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
if (isSetupWizard) {
// This would need to be updated for devices with multiple sensors of the same modality
mIsFaceEnrollable = !faceProperties.isEmpty()
&& faceManager.getEnrolledFaces(mUserId).size() == 0;
mIsFingerprintEnrollable = !fpProperties.isEmpty()
&& fingerprintManager.getEnrolledFingerprints(mUserId).size() == 0;
} else {
// This would need to be updated for devices with multiple sensors of the same modality
mIsFaceEnrollable = !faceProperties.isEmpty()
&& faceManager.getEnrolledFaces(mUserId).size()
< faceProperties.get(0).maxEnrollmentsPerUser;
mIsFingerprintEnrollable = !fpProperties.isEmpty()
&& fingerprintManager.getEnrolledFingerprints(mUserId).size()
< fpProperties.get(0).maxEnrollmentsPerUser;
if (mParentalConsentHelper != null) {
mParentalConsentHelper.updateGatekeeperHandle(data);
}
mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId,
mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
mMultiBiometricEnrollHelper.startNextStep();
}
private boolean userHasPassword(int userId) {
@@ -310,6 +412,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
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);
@@ -323,6 +426,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
private void launchConfirmLock() {
Log.d(TAG, "launchConfirmLock");
final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this);
builder.setRequestCode(REQUEST_CONFIRM_LOCK)
.setRequestGatekeeperPasswordHandle(true)
@@ -346,7 +450,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
* @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class,
* etc).
*/
private void launchEnrollActivity(@NonNull Intent intent) {
private void launchSingleSensorEnrollActivity(@NonNull Intent intent) {
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
byte[] hardwareAuthToken = null;
if (this instanceof InternalActivity) {
@@ -362,7 +466,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// 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);
launchEnrollActivity(intent);
launchSingleSensorEnrollActivity(intent);
}
private void launchFingerprintOnlyEnroll() {
@@ -374,12 +478,18 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
} else {
intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent());
}
launchEnrollActivity(intent);
launchSingleSensorEnrollActivity(intent);
}
private void launchFaceOnlyEnroll() {
final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent());
launchEnrollActivity(intent);
launchSingleSensorEnrollActivity(intent);
}
private void launchFaceAndFingerprintEnroll() {
mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId,
mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
mMultiBiometricEnrollHelper.startNextStep();
}
@Override

View File

@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -50,12 +51,15 @@ import com.google.android.setupdesign.util.ThemeHelper;
*/
public abstract class BiometricEnrollBase extends InstrumentedActivity {
private static final String TAG = "BiometricEnrollBase";
public static final String EXTRA_FROM_SETTINGS_SUMMARY = "from_settings_summary";
public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
public static final String EXTRA_KEY_REQUIRE_VISION = "accessibility_vision";
public static final String EXTRA_KEY_REQUIRE_DIVERSITY = "accessibility_diversity";
public static final String EXTRA_KEY_SENSOR_ID = "sensor_id";
public static final String EXTRA_KEY_CHALLENGE = "challenge";
public static final String EXTRA_KEY_MODALITY = "sensor_modality";
/**
* Used by the choose fingerprint wizard to indicate the wizard is
@@ -84,11 +88,26 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
*/
public static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2;
/**
* Used by consent screens to indicate that consent was granted. Extras, such as
* EXTRA_KEY_MODALITY, will be included in the result to provide details about the
* consent that was granted.
*/
public static final int RESULT_CONSENT_GRANTED = RESULT_FIRST_USER + 3;
/**
* Used by consent screens to indicate that consent was denied. Extras, such as
* EXTRA_KEY_MODALITY, will be included in the result to provide details about the
* consent that was not granted.
*/
public static final int RESULT_CONSENT_DENIED = RESULT_FIRST_USER + 4;
public static final int CHOOSE_LOCK_GENERIC_REQUEST = 1;
public static final int BIOMETRIC_FIND_SENSOR_REQUEST = 2;
public static final int LEARN_MORE_REQUEST = 3;
public static final int CONFIRM_REQUEST = 4;
public static final int ENROLL_REQUEST = 5;
/**
* Request code when starting another biometric enrollment from within a biometric flow. For
* example, when starting fingerprint enroll after face enroll.
@@ -242,6 +261,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
}
protected void launchConfirmLock(int titleResId) {
Log.d(TAG, "launchConfirmLock");
final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this);
builder.setRequestCode(CONFIRM_REQUEST)
.setTitle(getString(titleResId))

View File

@@ -294,15 +294,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
mConfirmingCredentials = false;
if (resultCode == RESULT_FINISHED) {
updatePasswordQuality();
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
mSensorId = sensorId;
mChallenge = challenge;
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, challenge);
BiometricUtils.removeGatekeeperPasswordHandle(this, data);
getNextButton().setEnabled(true);
}));
final boolean handled = onSetOrConfirmCredentials(data);
if (!handled) {
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
mSensorId = sensorId;
mChallenge = challenge;
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId,
challenge);
BiometricUtils.removeGatekeeperPasswordHandle(this, data);
getNextButton().setEnabled(true);
}));
}
} else {
setResult(resultCode, data);
finish();
@@ -310,15 +314,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
} else if (requestCode == CONFIRM_REQUEST) {
mConfirmingCredentials = false;
if (resultCode == RESULT_OK && data != null) {
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
mSensorId = sensorId;
mChallenge = challenge;
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, challenge);
BiometricUtils.removeGatekeeperPasswordHandle(this, data);
getNextButton().setEnabled(true);
}));
final boolean handled = onSetOrConfirmCredentials(data);
if (!handled) {
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
getNextButton().setEnabled(false);
getChallenge(((sensorId, userId, challenge) -> {
mSensorId = sensorId;
mChallenge = challenge;
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId,
challenge);
BiometricUtils.removeGatekeeperPasswordHandle(this, data);
getNextButton().setEnabled(true);
}));
}
} else {
setResult(resultCode, data);
finish();
@@ -335,6 +343,18 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Called after confirming credentials. Can be used to prevent the default
* behavior of immediately calling #getChallenge (useful to things like intro
* consent screens that don't actually do enrollment and will later start an
* activity that does).
*
* @return True if the default behavior should be skipped and handled by this method instead.
*/
protected boolean onSetOrConfirmCredentials(@Nullable Intent data) {
return false;
}
protected void onCancelButtonClick(View view) {
finish();
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 2021 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.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_MODALITY;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.biometrics.face.FaceEnrollParentalConsent;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollParentalConsent;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.util.WizardManagerHelper;
/**
* Helper for {@link BiometricEnrollActivity} to ask for parental consent prior to actual user
* enrollment.
*/
public class ParentalConsentHelper {
private static final String KEY_FACE_CONSENT = "face";
private static final String KEY_FINGERPRINT_CONSENT = "fingerprint";
private final boolean mRequireFace;
private final boolean mRequireFingerprint;
private long mGkPwHandle;
@Nullable
private Boolean mConsentFace;
@Nullable
private Boolean mConsentFingerprint;
/**
* Helper for aggregating user consent.
*
* @param requireFace if face consent should be shown
* @param requireFingerprint if fingerprint consent should be shown
* @param gkPwHandle for launched intents
*/
public ParentalConsentHelper(boolean requireFace, boolean requireFingerprint,
@Nullable Long gkPwHandle) {
mRequireFace = requireFace;
mRequireFingerprint = requireFingerprint;
mGkPwHandle = gkPwHandle != null ? gkPwHandle : 0L;
}
/**
* Updated the handle used for launching activities
*
* @param data result intent for credential verification
*/
public void updateGatekeeperHandle(Intent data) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
}
/**
* Launch the next consent screen.
*
* @param activity root activity
* @param requestCode request code to launch new activity
* @param resultCode result code of the last consent launch
* @param data result data from the last consent launch
* @return true if a consent activity was launched or false when complete
*/
public boolean launchNext(@NonNull Activity activity, int requestCode, int resultCode,
@Nullable Intent data) {
if (data != null) {
switch (data.getIntExtra(EXTRA_KEY_MODALITY, TYPE_NONE)) {
case TYPE_FACE:
mConsentFace = isConsent(resultCode, mConsentFace);
break;
case TYPE_FINGERPRINT:
mConsentFingerprint = isConsent(resultCode, mConsentFingerprint);
break;
}
}
return launchNext(activity, requestCode);
}
@Nullable
private static Boolean isConsent(int resultCode, @Nullable Boolean defaultValue) {
switch (resultCode) {
case RESULT_CONSENT_GRANTED:
return true;
case RESULT_CONSENT_DENIED:
return false;
}
return defaultValue;
}
/** @see #launchNext(Activity, int, int, Intent) */
public boolean launchNext(@NonNull Activity activity, int requestCode) {
final Intent intent = getNextConsentIntent(activity);
if (intent != null) {
WizardManagerHelper.copyWizardManagerExtras(activity.getIntent(), intent);
if (mGkPwHandle != 0) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
}
activity.startActivityForResult(intent, requestCode);
return true;
}
return false;
}
@Nullable
private Intent getNextConsentIntent(@NonNull Context context) {
if (mRequireFace && mConsentFace == null) {
return new Intent(context, FaceEnrollParentalConsent.class);
}
if (mRequireFingerprint && mConsentFingerprint == null) {
return new Intent(context, FingerprintEnrollParentalConsent.class);
}
return null;
}
/**
* Get the result of all consent requests.
*
* This should be called when {@link #launchNext(Activity, int, int, Intent)} returns false
* to indicate that all responses have been recorded.
*
* @return The aggregate consent status.
*/
@NonNull
public Bundle getConsentResult() {
final Bundle result = new Bundle();
result.putBoolean(KEY_FACE_CONSENT, mConsentFace != null ? mConsentFace : false);
result.putBoolean(KEY_FINGERPRINT_CONSENT,
mConsentFingerprint != null ? mConsentFingerprint : false);
return result;
}
/** @return If the result bundle contains consent for face authentication. */
public static boolean hasFaceConsent(@NonNull Bundle bundle) {
return bundle.getBoolean(KEY_FACE_CONSENT, false);
}
/** @return If the result bundle contains consent for fingerprint authentication. */
public static boolean hasFingerprintConsent(@NonNull Bundle bundle) {
return bundle.getBoolean(KEY_FINGERPRINT_CONSENT, false);
}
}

View File

@@ -76,31 +76,71 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
final ImageView iconGlasses = findViewById(R.id.icon_glasses);
final ImageView iconLooking = findViewById(R.id.icon_looking);
final ImageView iconSecurity = findViewById(R.id.icon_security);
iconGlasses.getBackground().setColorFilter(getIconColorFilter());
iconLooking.getBackground().setColorFilter(getIconColorFilter());
iconSecurity.getBackground().setColorFilter(getIconColorFilter());
final TextView infoMessageGlasses = findViewById(R.id.info_message_glasses);
final TextView infoMessageLooking = findViewById(R.id.info_message_looking);
final TextView howMessage = findViewById(R.id.how_message);
final TextView inControlTitle = findViewById(R.id.title_in_control);
final TextView inControlMessage = findViewById(R.id.message_in_control);
infoMessageGlasses.setText(getInfoMessageGlasses());
infoMessageLooking.setText(getInfoMessageLooking());
howMessage.setText(getHowMessage());
inControlTitle.setText(getInControlTitle());
inControlMessage.setText(getInControlMessage());
mFaceManager = Utils.getFaceManagerOrNull(this);
mFaceFeatureProvider = FeatureFactory.getFactory(getApplicationContext())
.getFaceFeatureProvider();
// This path is an entry point for SetNewPasswordController, e.g.
// adb shell am start -a android.app.action.SET_NEW_PASSWORD
if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
mFooterBarMixin.getPrimaryButton().setEnabled(false);
// We either block on generateChallenge, or need to gray out the "next" button until
// the challenge is ready. Let's just do this for now.
mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
mSensorId = sensorId;
mChallenge = challenge;
mFooterBarMixin.getPrimaryButton().setEnabled(true);
});
if (generateChallengeOnCreate()) {
mFooterBarMixin.getPrimaryButton().setEnabled(false);
// We either block on generateChallenge, or need to gray out the "next" button until
// the challenge is ready. Let's just do this for now.
mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId,
challenge);
mSensorId = sensorId;
mChallenge = challenge;
mFooterBarMixin.getPrimaryButton().setEnabled(true);
});
}
}
}
protected boolean generateChallengeOnCreate() {
return true;
}
@StringRes
protected int getInfoMessageGlasses() {
return R.string.security_settings_face_enroll_introduction_info_glasses;
}
@StringRes
protected int getInfoMessageLooking() {
return R.string.security_settings_face_enroll_introduction_info_looking;
}
@StringRes
protected int getHowMessage() {
return R.string.security_settings_face_enroll_introduction_how_message;
}
@StringRes
protected int getInControlTitle() {
return R.string.security_settings_face_enroll_introduction_control_title;
}
@StringRes
protected int getInControlMessage() {
return R.string.security_settings_face_enroll_introduction_control_message;
}
@Override
protected boolean isDisabledByAdmin() {
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2021 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.face;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.settings.R;
/**
* Displays parental consent information for face authentication.
*
* TODO(b/188847063): swap strings for consent screen
*/
public class FaceEnrollParentalConsent extends FaceEnrollIntroduction {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDescriptionText(R.string.security_settings_face_enroll_introduction_consent_message);
}
@Override
protected void onNextButtonClick(View view) {
onConsentResult(true /* granted */);
}
@Override
protected void onSkipButtonClick(View view) {
onConsentResult(false /* granted */);
}
private void onConsentResult(boolean granted) {
final Intent result = new Intent();
result.putExtra(EXTRA_KEY_MODALITY, TYPE_FACE);
setResult(granted ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED, result);
finish();
}
@Override
protected boolean onSetOrConfirmCredentials(@Nullable Intent data) {
// prevent challenge from being generated by default
return true;
}
@Override
protected boolean generateChallengeOnCreate() {
return false;
}
@Override
@StringRes
protected int getInfoMessageGlasses() {
return R.string.security_settings_face_enroll_introduction_info_consent_glasses;
}
@Override
@StringRes
protected int getInfoMessageLooking() {
return R.string.security_settings_face_enroll_introduction_info_consent_looking;
}
@Override
@StringRes
protected int getHowMessage() {
return R.string.security_settings_face_enroll_introduction_how_consent_message;
}
@Override
@StringRes
protected int getInControlTitle() {
return R.string.security_settings_face_enroll_introduction_control_consent_title;
}
@Override
@StringRes
protected int getInControlMessage() {
return R.string.security_settings_face_enroll_introduction_control_consent_message;
}
@Override
protected int getHeaderResDefault() {
return R.string.security_settings_face_enroll_consent_introduction_title;
}
}

View File

@@ -64,21 +64,62 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
super.onCreate(savedInstanceState);
final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint);
final ImageView iconLocked = findViewById(R.id.icon_locked);
final ImageView iconDelete = findViewById(R.id.icon_delete);
final ImageView iconInfo = findViewById(R.id.icon_info);
final ImageView iconLink = findViewById(R.id.icon_link);
iconFingerprint.getDrawable().setColorFilter(getIconColorFilter());
iconLocked.getDrawable().setColorFilter(getIconColorFilter());
iconDelete.getDrawable().setColorFilter(getIconColorFilter());
iconInfo.getDrawable().setColorFilter(getIconColorFilter());
iconLink.getDrawable().setColorFilter(getIconColorFilter());
final TextView footerMessage2 = findViewById(R.id.footer_message_2);
final TextView footerMessage3 = findViewById(R.id.footer_message_3);
final TextView footerMessage4 = findViewById(R.id.footer_message_4);
final TextView footerMessage5 = findViewById(R.id.footer_message_5);
footerMessage2.setText(getFooterMessage2());
footerMessage3.setText(getFooterMessage3());
footerMessage4.setText(getFooterMessage4());
footerMessage5.setText(getFooterMessage5());
final TextView footerTitle1 = findViewById(R.id.footer_title_1);
final TextView footerTitle2 = findViewById(R.id.footer_title_2);
footerTitle1.setText(getFooterTitle1());
footerTitle2.setText(getFooterTitle2());
}
@StringRes
int getNegativeButtonTextId() {
return R.string.security_settings_fingerprint_enroll_introduction_skip;
}
@StringRes
protected int getFooterTitle1() {
return R.string.security_settings_fingerprint_enroll_introduction_footer_title_1;
}
@StringRes
protected int getFooterTitle2() {
return R.string.security_settings_fingerprint_enroll_introduction_footer_title_2;
}
@StringRes
protected int getFooterMessage2() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2;
}
@StringRes
protected int getFooterMessage3() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3;
}
@StringRes
protected int getFooterMessage4() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4;
}
@StringRes
protected int getFooterMessage5() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5;
}
@Override
protected boolean isDisabledByAdmin() {
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2021 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.fingerprint;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.settings.R;
/**
* Displays parental consent information for fingerprint authentication.
*
* TODO(b/188847063): swap strings for consent screen
*/
public class FingerprintEnrollParentalConsent extends FingerprintEnrollIntroduction {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDescriptionText(
R.string.security_settings_fingerprint_enroll_introduction_consent_message);
}
@Override
protected void onNextButtonClick(View view) {
onConsentResult(true /* granted */);
}
@Override
protected void onSkipButtonClick(View view) {
onConsentResult(false /* granted */);
}
private void onConsentResult(boolean granted) {
final Intent result = new Intent();
result.putExtra(EXTRA_KEY_MODALITY, TYPE_FINGERPRINT);
setResult(granted ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED, result);
finish();
}
@Override
protected boolean onSetOrConfirmCredentials(@Nullable Intent data) {
// prevent challenge from being generated by default
return true;
}
@StringRes
@Override
protected int getFooterTitle1() {
return R.string.security_settings_fingerprint_enroll_introduction_footer_title_consent_1;
}
@StringRes
@Override
protected int getFooterMessage2() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_2;
}
@StringRes
@Override
protected int getFooterMessage3() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_3;
}
@StringRes
protected int getFooterMessage4() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_4;
}
@StringRes
protected int getFooterMessage5() {
return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5;
}
@Override
protected int getHeaderResDefault() {
return R.string.security_settings_fingerprint_enroll_consent_introduction_title;
}
}