2/n: Add default implementation for multi-biometric enroll

1) Adds a layout for multi-biometric selection in BiometricEnrollActivity
2) Adds widgets for checkboxes
3) Shows ConfirmLock*/ChooseLock* for multi-biometric devices in
   BiometricEnrollActivity
4) finish()'s when loses foreground
5) Adds default string for ChooseLock* and multi-biometrics, e.g.
   "Set up Password + Biometrics", as well as associated plumbing
   to bring the user back to BiometricEnrollActivity once the
   credential is enrolled
6) When max templates enrolled, checkbox becomes disabled and
   description string is updated

Bug: 162341940
Bug: 152242790
Fixes: 161742393

No effect on existing devices with the following:
Test: adb shell am start -a android.settings.BIOMETRIC_ENROLL
Test: SUW
Test: make -j RunSettingsRoboTests

Exempt-From-Owner-Approval: Biometric-related change
to EncryptionInterstitial

Change-Id: I855460d50228ace24d4ec5fbe330f02ab406cc02
This commit is contained in:
Kevin Chyn
2020-09-09 13:28:28 -07:00
parent eb8c0f14ea
commit 87bb772e16
35 changed files with 1281 additions and 270 deletions

View File

@@ -111,6 +111,8 @@ public class EncryptionInterstitial extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
Intent intent = getActivity().getIntent();
mRequestedPasswordQuality = intent.getIntExtra(EXTRA_PASSWORD_QUALITY, 0);
mUnlockMethodIntent = intent.getParcelableExtra(EXTRA_UNLOCK_METHOD_INTENT);
@@ -121,6 +123,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_pattern_for_fingerprint :
forFace ?
R.string.encryption_interstitial_message_pattern_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_pattern_for_biometrics :
R.string.encryption_interstitial_message_pattern;
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
@@ -129,6 +133,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_pin_for_fingerprint :
forFace ?
R.string.encryption_interstitial_message_pin_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_pin_for_biometrics :
R.string.encryption_interstitial_message_pin;
break;
default:
@@ -136,6 +142,8 @@ public class EncryptionInterstitial extends SettingsActivity {
R.string.encryption_interstitial_message_password_for_fingerprint :
forFace ?
R.string.encryption_interstitial_message_password_for_face :
forBiometrics ?
R.string.encryption_interstitial_message_password_for_biometrics :
R.string.encryption_interstitial_message_password;
break;
}

View File

@@ -21,23 +21,36 @@ 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.BiometricManager;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.face.FaceEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockPattern;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.GlifLayout;
import java.util.List;
/**
* Trampoline activity launched by the {@code android.settings.BIOMETRIC_ENROLL} action which
@@ -49,18 +62,53 @@ 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;
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";
private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials";
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;
@Nullable private Long mGkPwHandle;
private BiometricEnrollCheckbox mCheckboxFace;
private BiometricEnrollCheckbox mCheckboxFingerprint;
@Nullable private MultiBiometricEnrollHelper mMultiBiometricEnrollHelper;
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (this instanceof InternalActivity) {
mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
}
if (savedInstanceState != null) {
mConfirmingCredentials = savedInstanceState
.getBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, false);
if (savedInstanceState.containsKey(SAVED_STATE_GK_PW_HANDLE)) {
mGkPwHandle = savedInstanceState.getLong(SAVED_STATE_GK_PW_HANDLE);
}
}
// Put the theme in the intent so it gets propagated to other activities in the flow
final Intent intent = getIntent();
if (intent.getStringExtra(WizardManagerHelper.EXTRA_THEME) == null) {
intent.putExtra(
WizardManagerHelper.EXTRA_THEME,
SetupWizardUtils.getThemeString(intent));
}
// Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL.
final int authenticators = getIntent().getIntExtra(
final int authenticators = intent.getIntExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK);
Log.d(TAG, "Authenticators: " + authenticators);
@@ -73,9 +121,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (isSetupWizard) {
if (hasFeatureFace && hasFeatureFingerprint) {
// TODO(b/162341940, b/152242790) this should show a multi-biometric selection
// screen
launchFingerprintOnlyEnroll();
setupForMultiBiometricEnroll();
} else if (hasFeatureFace) {
launchFaceOnlyEnroll();
} else if (hasFeatureFingerprint) {
@@ -98,9 +144,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
launchCredentialOnlyEnroll();
} else if (hasFeatureFace && hasFeatureFingerprint) {
// TODO(b/162341940, b/152242790) this should show a multi-biometric selection
// screen
launchFingerprintOnlyEnroll();
setupForMultiBiometricEnroll();
} else if (hasFeatureFingerprint) {
launchFingerprintOnlyEnroll();
} else if (hasFeatureFace) {
@@ -112,30 +156,216 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials);
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 (mMultiBiometricEnrollHelper == null) {
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
switch (requestCode) {
case REQUEST_CHOOSE_LOCK:
mConfirmingCredentials = false;
if (resultCode == ChooseLockPattern.RESULT_FINISHED) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
} else {
Log.d(TAG, "Unknown result for chooseLock: " + resultCode);
setResult(resultCode);
finish();
}
break;
case REQUEST_CONFIRM_LOCK:
mConfirmingCredentials = false;
if (resultCode == RESULT_OK) {
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
} else {
Log.d(TAG, "Unknown result for confirmLock: " + resultCode);
finish();
}
break;
default:
Log.d(TAG, "Unknown requestCode: " + requestCode + ", finishing");
finish();
}
} else {
mMultiBiometricEnrollHelper.onActivityResult(requestCode, resultCode, data);
}
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
resid = SetupWizardUtils.getTheme(getIntent());
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
super.onApplyThemeResource(theme, resid, first);
}
@Override
protected void onStop() {
super.onStop();
if (mConfirmingCredentials || mMultiBiometricEnrollHelper != null) {
return;
}
if (!isChangingConfigurations()) {
Log.d(TAG, "Finishing in onStop");
finish();
}
}
private void setupForMultiBiometricEnroll() {
setContentView(R.layout.biometric_enroll_layout);
mCheckboxFace = findViewById(R.id.checkbox_face);
mCheckboxFingerprint = findViewById(R.id.checkbox_fingerprint);
mCheckboxFace.setListener(this::updateNextButton);
mCheckboxFingerprint.setListener(this::updateNextButton);
final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
final FaceManager faceManager = getSystemService(FaceManager.class);
final List<FingerprintSensorProperties> fpProperties =
fingerprintManager.getSensorProperties();
final List<FaceSensorProperties> faceProperties = faceManager.getSensorProperties();
// This would need to be updated for devices with multiple sensors of the same modality
final boolean maxFacesEnrolled = faceManager.getEnrolledFaces(mUserId).size()
>= faceProperties.get(0).maxTemplatesAllowed;
final boolean maxFingerprintsEnrolled = fingerprintManager.getEnrolledFingerprints(mUserId)
.size() >= fpProperties.get(0).maxTemplatesAllowed;
if (maxFacesEnrolled) {
mCheckboxFace.setEnabled(false);
mCheckboxFace.setDescription(R.string.face_intro_error_max);
}
if (maxFingerprintsEnrolled) {
mCheckboxFingerprint.setEnabled(false);
mCheckboxFingerprint.setDescription(R.string.fingerprint_intro_error_max);
}
final FooterBarMixin footerBarMixin = ((GlifLayout) findViewById(R.id.setup_wizard_layout))
.getMixin(FooterBarMixin.class);
footerBarMixin.setSecondaryButton(new FooterButton.Builder(this)
.setText(R.string.multi_biometric_enroll_skip)
.setListener(this::onButtonNegative)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build());
footerBarMixin.setPrimaryButton(new FooterButton.Builder(this)
.setText(R.string.multi_biometric_enroll_next)
.setListener(this::onButtonPositive)
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(R.style.SudGlifButton_Primary)
.build());
footerBarMixin.getSecondaryButton().setVisibility(View.VISIBLE);
footerBarMixin.getPrimaryButton().setVisibility(View.VISIBLE);
if (!mConfirmingCredentials && mGkPwHandle == null) {
mConfirmingCredentials = true;
if (!userHasPassword(mUserId)) {
launchChooseLock();
} else {
launchConfirmLock();
}
}
}
private void updateNextButton(View view) {
final boolean canEnrollAny = canEnrollFace() || canEnrollFingerprint();
final FooterBarMixin footerBarMixin = ((GlifLayout) findViewById(R.id.setup_wizard_layout))
.getMixin(FooterBarMixin.class);
footerBarMixin.getPrimaryButton().setEnabled(canEnrollAny);
}
private void onButtonPositive(View view) {
// Start the state machine according to checkboxes, taking max enrolled into account
mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId,
canEnrollFace(), canEnrollFingerprint(), mGkPwHandle);
mMultiBiometricEnrollHelper.startNextStep();
}
private void onButtonNegative(View view) {
setResult(RESULT_SKIP);
finish();
}
private boolean canEnrollFace() {
return mCheckboxFace.isEnabled() && mCheckboxFace.isChecked();
}
private boolean canEnrollFingerprint() {
return mCheckboxFingerprint.isEnabled() && mCheckboxFingerprint.isChecked();
}
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.MINIMUM_QUALITY_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, 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, which use
* FLAG_ACTIVITY_FORWARD_RESULT path.
*
* @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class,
* etc).
*/
private void launchEnrollActivity(@NonNull Intent intent) {
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
byte[] hardwareAuthToken = null;
if (this instanceof InternalActivity) {
// Propagate challenge and user Id from ChooseLockGeneric.
final byte[] token = getIntent()
.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
final int userId = getIntent()
.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
final long gkPwHandle = getIntent().getLongExtra(
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
if (gkPwHandle != 0L) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
}
hardwareAuthToken = getIntent().getByteArrayExtra(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
}
startActivity(intent);
finish();
BiometricUtils.launchEnrollForResult(this, intent, 0 /* requestCode */, hardwareAuthToken,
null /* gkPwHandle */, mUserId);
}
private void launchCredentialOnlyEnroll() {
@@ -152,40 +382,18 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
// ChooseLockGeneric can request to start fingerprint enroll bypassing the intro screen.
if (getIntent().getBooleanExtra(EXTRA_SKIP_INTRO, false)
&& this instanceof InternalActivity) {
intent = getFingerprintFindSensorIntent();
intent = BiometricUtils.getFingerprintFindSensorIntent(this, getIntent());
} else {
intent = getFingerprintIntroIntent();
intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent());
}
launchEnrollActivity(intent);
}
private void launchFaceOnlyEnroll() {
final Intent intent = getFaceIntroIntent();
final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent());
launchEnrollActivity(intent);
}
private Intent getFingerprintFindSensorIntent() {
Intent intent = new Intent(this, FingerprintEnrollFindSensor.class);
SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent;
}
private Intent getFingerprintIntroIntent() {
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
Intent intent = new Intent(this, SetupFingerprintEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
return intent;
} else {
return new Intent(this, FingerprintEnrollIntroduction.class);
}
}
private Intent getFaceIntroIntent() {
Intent intent = new Intent(this, FaceEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
return intent;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY;

View File

@@ -57,6 +57,9 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
* starting the next activity. However, this leads to broken 'Back'
* behavior. So, now an activity does not finish itself until it gets this
* result.
*
* This must be the same as
* {@link com.android.settings.password.ChooseLockPattern#RESULT_FINISHED}
*/
public static final int RESULT_FINISHED = RESULT_FIRST_USER;
@@ -78,6 +81,11 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
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.
*/
public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6;
protected boolean mLaunchedConfirmLock;
protected byte[] mToken;

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2020 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 android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.settings.R;
/**
* Widget contain space for an icon, title, description, and checkbox. On devices with multiple
* biometric sensors, allows users to choose sensors during {@link BiometricEnrollActivity}.
*/
public class BiometricEnrollCheckbox extends LinearLayout {
@NonNull private final CheckBox mCheckBox;
@NonNull private final TextView mDescriptionView;
@Nullable private View.OnClickListener mListener;
public BiometricEnrollCheckbox(Context context) {
this(context, null /* attrs */);
}
public BiometricEnrollCheckbox(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0 /* defStyleAttr */);
}
public BiometricEnrollCheckbox(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
}
public BiometricEnrollCheckbox(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(R.layout.biometric_enroll_checkbox,
this, true /* attachToRoot */);
mCheckBox = findViewById(R.id.checkbox);
final ImageView iconView = findViewById(R.id.icon);
final TextView titleView = findViewById(R.id.title);
mDescriptionView = findViewById(R.id.description);
setOnClickListener(view -> {
if (isEnabled()) {
mCheckBox.toggle();
}
if (mListener != null) {
mListener.onClick(view);
}
});
final TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.BiometricEnrollCheckbox);
try {
final Drawable icon =
a.getDrawable(R.styleable.BiometricEnrollCheckbox_icon);
final CharSequence title =
a.getText(R.styleable.BiometricEnrollCheckbox_title);
final CharSequence description =
a.getText(R.styleable.BiometricEnrollCheckbox_description);
iconView.setImageDrawable(icon);
titleView.setText(title);
mDescriptionView.setText(description);
} finally {
a.recycle();
}
}
public void setListener(View.OnClickListener listener) {
mListener = listener;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
mCheckBox.setEnabled(enabled);
}
public boolean isChecked() {
return mCheckBox.isChecked();
}
public void setDescription(@StringRes int resId) {
mDescriptionView.setText(resId);
}
}

View File

@@ -21,7 +21,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -29,9 +29,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupChooseLockGeneric;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -43,6 +41,8 @@ import com.google.android.setupdesign.span.LinkSpan;
public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
implements LinkSpan.OnClickListener {
private static final String TAG = "BiometricEnrollIntroduction";
private static final String KEY_CONFIRMING_CREDENTIALS = "confirming_credentials";
private UserManager mUserManager;
@@ -164,7 +164,8 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
// No password registered, launch into enrollment wizard.
mConfirmingCredentials = true;
launchChooseLock();
} else if (!BiometricUtils.containsGatekeeperPassword(getIntent()) && mToken == null) {
} else if (!BiometricUtils.containsGatekeeperPasswordHandle(getIntent())
&& mToken == null) {
// It's possible to have a token but mLaunchedConfirmLock == false, since
// ChooseLockGeneric can pass us a token.
mConfirmingCredentials = true;
@@ -220,7 +221,7 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
}
private void launchChooseLock() {
Intent intent = getChooseLockIntent();
Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent());
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true);
@@ -240,27 +241,11 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
if (mUserId != UserHandle.USER_NULL) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
}
BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
}
protected Intent getChooseLockIntent() {
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
// Default to PIN lock in setup wizard
Intent intent = new Intent(this, SetupChooseLockGeneric.class);
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
intent.putExtra(
LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
intent.putExtra(ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, true);
}
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
return intent;
} else {
return new Intent(this, ChooseLockGeneric.class);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST) {
@@ -301,6 +286,12 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
}
} else if (requestCode == LEARN_MORE_REQUEST) {
overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out);
} else if (requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST) {
Log.d(TAG, "ENROLL_NEXT_BIOMETRIC_REQUEST, result: " + resultCode);
if (resultCode != RESULT_CANCELED) {
setResult(resultCode, data);
finish();
}
}
super.onActivityResult(requestCode, resultCode, data);
}

View File

@@ -16,16 +16,35 @@
package com.android.settings.biometrics;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.os.storage.StorageManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.SetupWizardUtils;
import com.android.settings.biometrics.face.FaceEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupChooseLockGeneric;
import com.google.android.setupcompat.util.WizardManagerHelper;
/**
* Common biometric utilities.
*/
public class BiometricUtils {
private static final String TAG = "BiometricUtils";
/**
* Given the result from confirming or choosing a credential, request Gatekeeper to generate
* a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge.
@@ -36,24 +55,29 @@ public class BiometricUtils {
* @param challenge Unique biometric challenge from FingerprintManager/FaceManager
* @return
*/
public static byte[] requestGatekeeperHat(Context context, Intent result, int userId,
long challenge) {
final long gatekeeperPasswordHandle = result.getLongExtra(
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
if (gatekeeperPasswordHandle == 0L) {
public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result,
int userId, long challenge) {
if (!containsGatekeeperPasswordHandle(result)) {
throw new IllegalStateException("Gatekeeper Password is missing!!");
}
final long gatekeeperPasswordHandle = result.getLongExtra(
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
return requestGatekeeperHat(context, gatekeeperPasswordHandle, userId, challenge);
}
public static byte[] requestGatekeeperHat(@NonNull Context context, long gkPwHandle, int userId,
long challenge) {
final LockPatternUtils utils = new LockPatternUtils(context);
return utils.verifyGatekeeperPasswordHandle(gatekeeperPasswordHandle, challenge, userId)
return utils.verifyGatekeeperPasswordHandle(gkPwHandle, challenge, userId)
.getGatekeeperHAT();
}
public static boolean containsGatekeeperPassword(Intent data) {
if (data == null) {
return false;
}
return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L) != 0L;
public static boolean containsGatekeeperPasswordHandle(@Nullable Intent data) {
return data != null && data.hasExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE);
}
public static long getGatekeeperPasswordHandle(@NonNull Intent data) {
return data.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
}
/**
@@ -64,16 +88,157 @@ public class BiometricUtils {
* @param context Caller's context
* @param data The onActivityResult intent from ChooseLock* or ConfirmLock*
*/
public static void removeGatekeeperPasswordHandle(Context context, Intent data) {
public static void removeGatekeeperPasswordHandle(@NonNull Context context,
@Nullable Intent data) {
if (data == null) {
return;
}
final long gatekeeperPasswordsHandle = data.getLongExtra(
ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 0L);
if (gatekeeperPasswordsHandle == 0L) {
if (!containsGatekeeperPasswordHandle(data)) {
return;
}
removeGatekeeperPasswordHandle(context, getGatekeeperPasswordHandle(data));
}
public static void removeGatekeeperPasswordHandle(@NonNull Context context, long handle) {
final LockPatternUtils utils = new LockPatternUtils(context);
utils.removeGatekeeperPasswordHandle(gatekeeperPasswordsHandle);
utils.removeGatekeeperPasswordHandle(handle);
Log.d(TAG, "Removed handle");
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting ChooseLock*
*/
public static Intent getChooseLockIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
// Default to PIN lock in setup wizard
Intent intent = new Intent(context, SetupChooseLockGeneric.class);
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
intent.putExtra(
LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment
.EXTRA_SHOW_OPTIONS_BUTTON, true);
}
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
} else {
return new Intent(context, ChooseLockGeneric.class);
}
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FingerprintEnrollFindSensor
*/
public static Intent getFingerprintFindSensorIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
Intent intent = new Intent(context, FingerprintEnrollFindSensor.class);
SetupWizardUtils.copySetupExtras(activityIntent, intent);
return intent;
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FingerprintEnrollIntroduction
*/
public static Intent getFingerprintIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
if (WizardManagerHelper.isAnySetupWizard(activityIntent)) {
Intent intent = new Intent(context, SetupFingerprintEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
} else {
return new Intent(context, FingerprintEnrollIntroduction.class);
}
}
/**
* @param context caller's context
* @param activityIntent The intent that started the caller's activity
* @return Intent for starting FaceEnrollIntroduction
*/
public static Intent getFaceIntroIntent(@NonNull Context context,
@NonNull Intent activityIntent) {
Intent intent = new Intent(context, FaceEnrollIntroduction.class);
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
return intent;
}
/**
* @param activity Reference to the calling activity, used to startActivity
* @param intent Intent pointing to the enrollment activity
* @param requestCode If non-zero, will invoke startActivityForResult instead of startActivity
* @param hardwareAuthToken HardwareAuthToken from Gatekeeper
* @param userId User to request enrollment for
*/
public static void launchEnrollForResult(@NonNull BiometricEnrollActivity activity,
@NonNull Intent intent, int requestCode,
@Nullable byte[] hardwareAuthToken, @Nullable Long gkPwHandle, int userId) {
if (hardwareAuthToken != null) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
hardwareAuthToken);
}
if (gkPwHandle != null) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gkPwHandle);
}
if (activity instanceof BiometricEnrollActivity.InternalActivity) {
intent.putExtra(Intent.EXTRA_USER_ID, userId);
}
if (requestCode != 0) {
activity.startActivityForResult(intent, requestCode);
} else {
activity.startActivity(intent);
activity.finish();
}
}
/**
* @param activity Activity that we want to check
* @return True if the activity is going through a multi-biometric enrollment flow.
*/
public static boolean isMultiBiometricEnrollmentFlow(@NonNull Activity activity) {
return activity.getIntent().hasExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE);
}
public static void copyMultiBiometricExtras(@NonNull Intent fromIntent,
@NonNull Intent toIntent) {
final PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra(
MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, null);
if (pendingIntent != null) {
toIntent.putExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE, pendingIntent);
}
}
/**
* If the current biometric enrollment (e.g. face) should be followed by another one (e.g.
* fingerprint) (see {@link #isMultiBiometricEnrollmentFlow(Activity)}), retrieves the
* PendingIntent pointing to the next enrollment and starts it. The caller will receive the
* result in onActivityResult.
* @return true if the next enrollment was started
*/
public static boolean tryStartingNextBiometricEnroll(@NonNull Activity activity,
int requestCode) {
final PendingIntent pendingIntent = (PendingIntent) activity.getIntent()
.getExtra(MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FACE);
if (pendingIntent != null) {
try {
Log.d(TAG, "Starting pendingIntent: " + pendingIntent);
IntentSender intentSender = pendingIntent.getIntentSender();
activity.startIntentSenderForResult(intentSender, requestCode,
null /* fillInIntent */, 0 /* flagMask */, 0 /* flagValues */,
0 /* extraFlags */);
return true;
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Pending intent canceled: " + e);
}
}
return false;
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2020 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 android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.settings.password.ChooseLockSettingsHelper;
/**
* Helper for {@link BiometricEnrollActivity} when multiple sensors exist on a device.
*/
public class MultiBiometricEnrollHelper {
private static final String TAG = "MultiBiometricEnrollHelper";
private static final int REQUEST_FACE_ENROLL = 3000;
private static final int REQUEST_FINGERPRINT_ENROLL = 3001;
public static final String EXTRA_ENROLL_AFTER_FACE = "enroll_after_face";
@NonNull private final BiometricEnrollActivity mActivity;
private final long mGkPwHandle;
private final int mUserId;
private final boolean mRequestEnrollFace;
private final boolean mRequestEnrollFingerprint;
MultiBiometricEnrollHelper(@NonNull BiometricEnrollActivity activity, int userId,
boolean enrollFace, boolean enrollFingerprint, long gkPwHandle) {
mActivity = activity;
mUserId = userId;
mGkPwHandle = gkPwHandle;
mRequestEnrollFace = enrollFace;
mRequestEnrollFingerprint = enrollFingerprint;
}
void startNextStep() {
if (mRequestEnrollFace) {
launchFaceEnroll();
} else if (mRequestEnrollFingerprint) {
launchFingerprintEnroll();
} else {
mActivity.setResult(BiometricEnrollIntroduction.RESULT_SKIP);
mActivity.finish();
}
}
private void launchFaceEnroll() {
final FaceManager faceManager = mActivity.getSystemService(FaceManager.class);
faceManager.generateChallenge((sensorId, challenge) -> {
final byte[] hardwareAuthToken = BiometricUtils.requestGatekeeperHat(mActivity,
mGkPwHandle, mUserId, challenge);
final Intent faceIntent = BiometricUtils.getFaceIntroIntent(mActivity,
mActivity.getIntent());
if (mRequestEnrollFingerprint) {
// Give FaceEnroll a pendingIntent pointing to fingerprint enrollment, so that it
// can be started when user skips or finishes face enrollment.
final Intent fpIntent = BiometricUtils.getFingerprintIntroIntent(mActivity,
mActivity.getIntent());
fpIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
final PendingIntent fpAfterFaceIntent = PendingIntent.getActivity(mActivity,
0 /* requestCode */, fpIntent, 0 /* flags */);
faceIntent.putExtra(EXTRA_ENROLL_AFTER_FACE, fpAfterFaceIntent);
}
BiometricUtils.launchEnrollForResult(mActivity, faceIntent, REQUEST_FACE_ENROLL,
hardwareAuthToken, mGkPwHandle, mUserId);
});
}
private void launchFingerprintEnroll() {
final FingerprintManager fingerprintManager = mActivity
.getSystemService(FingerprintManager.class);
fingerprintManager.generateChallenge(((sensorId, challenge) -> {
final byte[] hardwareAuthToken = BiometricUtils.requestGatekeeperHat(mActivity,
mGkPwHandle, mUserId, challenge);
final Intent intent = BiometricUtils.getFingerprintIntroIntent(mActivity,
mActivity.getIntent());
BiometricUtils.launchEnrollForResult(mActivity, intent, REQUEST_FINGERPRINT_ENROLL,
hardwareAuthToken, mGkPwHandle, mUserId);
}));
}
void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "RequestCode: " + requestCode + " resultCode: " + resultCode);
if (resultCode != Activity.RESULT_CANCELED) {
BiometricUtils.removeGatekeeperPasswordHandle(mActivity, mGkPwHandle);
mActivity.setResult(resultCode);
mActivity.finish();
}
}
}

View File

@@ -33,6 +33,7 @@ import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollBase;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.google.android.setupcompat.template.FooterBarMixin;
@@ -183,6 +184,7 @@ public class FaceEnrollEducation extends BiometricEnrollBase {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
}
intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
BiometricUtils.copyMultiBiometricExtras(getIntent(), intent);
final String flattenedString = getString(R.string.config_face_enroll);
if (!TextUtils.isEmpty(flattenedString)) {
ComponentName componentName = ComponentName.unflattenFromString(flattenedString);

View File

@@ -16,17 +16,23 @@
package com.android.settings.biometrics.face;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.IntentSender;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.biometrics.MultiBiometricEnrollHelper;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -37,6 +43,8 @@ import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.span.LinkSpan;
import com.google.android.setupdesign.template.RequireScrollMixin;
import java.util.List;
public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private static final String TAG = "FaceEnrollIntroduction";
@@ -44,6 +52,20 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private FaceManager mFaceManager;
private FaceFeatureProvider mFaceFeatureProvider;
@Override
protected void onCancelButtonClick(View view) {
if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST)) {
super.onCancelButtonClick(view);
}
}
@Override
protected void onSkipButtonClick(View view) {
if (!BiometricUtils.tryStartingNextBiometricEnroll(this, ENROLL_NEXT_BIOMETRIC_REQUEST)) {
super.onSkipButtonClick(view);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -53,25 +75,14 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
.getFaceFeatureProvider();
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_no_thanks)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
} else {
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_no_thanks)
.setListener(this::onCancelButtonClick)
.setButtonType(FooterButton.ButtonType.CANCEL)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
}
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_no_thanks)
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
);
FooterButton.Builder nextButtonBuilder = new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_agree)
@@ -99,13 +110,15 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
// 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.containsGatekeeperPassword(getIntent())) {
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((sensorId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
BiometricUtils.removeGatekeeperPasswordHandle(this, getIntent());
if (BiometricUtils.isMultiBiometricEnrollmentFlow(this)) {
BiometricUtils.removeGatekeeperPasswordHandle(this, getIntent());
}
mFooterBarMixin.getPrimaryButton().setEnabled(true);
});
}
@@ -160,8 +173,9 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
private boolean maxFacesEnrolled() {
if (mFaceManager != null) {
final int max = getResources().getInteger(
com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
final List<FaceSensorProperties> props = mFaceManager.getSensorProperties();
// This will need to be updated for devices with multiple face sensors.
final int max = props.get(0).maxTemplatesAllowed;
final int numEnrolledFaces = mFaceManager.getEnrolledFaces(mUserId).size();
return numEnrolledFaces >= max;
} else {

View File

@@ -236,7 +236,7 @@ public class FaceSettings extends DashboardFragment {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mToken == null && !BiometricUtils.containsGatekeeperPassword(data)) {
if (mToken == null && !BiometricUtils.containsGatekeeperPasswordHandle(data)) {
Log.e(TAG, "No credential");
finish();
}

View File

@@ -63,7 +63,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
// This is an entry point for SetNewPasswordController, e.g.
// adb shell am start -a android.app.action.SET_NEW_PASSWORD
if (mToken == null && BiometricUtils.containsGatekeeperPassword(getIntent())) {
if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
final FingerprintManager fpm = getSystemService(FingerprintManager.class);
fpm.generateChallenge((sensorId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
@@ -79,6 +79,9 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase {
} else if (mToken != null) {
// HAT passed in from somewhere else, such as FingerprintEnrollIntroduction
startLookingForFingerprint();
} else {
// There's something wrong with the enrollment flow, this should never happen.
throw new IllegalStateException("HAT and GkPwHandle both missing...");
}
View animationView = findViewById(R.id.fingerprint_sensor_location_animation);

View File

@@ -21,6 +21,7 @@ import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
@@ -28,6 +29,7 @@ import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricEnrollIntroduction;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -36,6 +38,8 @@ import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.span.LinkSpan;
import java.util.List;
public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
private static final String TAG = "FingerprintIntro";
@@ -45,13 +49,14 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFingerprintManager = Utils.getFingerprintManagerOrNull(this);
mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
mFooterBarMixin.setSecondaryButton(
new FooterButton.Builder(this)
.setText(R.string.security_settings_face_enroll_introduction_cancel)
.setListener(this::onCancelButtonClick)
.setText(getNegativeButtonTextId())
.setListener(this::onSkipButtonClick)
.setButtonType(FooterButton.ButtonType.SKIP)
.setTheme(R.style.SudGlifButton_Secondary)
.build()
@@ -67,6 +72,10 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
);
}
int getNegativeButtonTextId() {
return R.string.security_settings_fingerprint_enroll_introduction_no_thanks;
}
@Override
protected boolean isDisabledByAdmin() {
return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
@@ -117,8 +126,10 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected int checkMaxEnrolled() {
if (mFingerprintManager != null) {
final int max = getResources().getInteger(
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
final List<FingerprintSensorProperties> props =
mFingerprintManager.getSensorProperties();
// This will need to be updated for devices with multiple fingerprint sensors
final int max = props.get(0).maxTemplatesAllowed;
final int numEnrolledFingerprints =
mFingerprintManager.getEnrolledFingerprints(mUserId).size();
if (numEnrolledFingerprints >= max) {
@@ -147,7 +158,12 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
@Override
protected Intent getEnrollingIntent() {
return new Intent(this, FingerprintEnrollFindSensor.class);
final Intent intent = new Intent(this, FingerprintEnrollFindSensor.class);
if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
return intent;
}
@Override

View File

@@ -570,7 +570,7 @@ public class FingerprintSettings extends SubSettings {
if (requestCode == CONFIRM_REQUEST || requestCode == CHOOSE_LOCK_GENERIC_REQUEST) {
mLaunchedConfirm = false;
if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
if (data != null && BiometricUtils.containsGatekeeperPassword(data)) {
if (data != null && BiometricUtils.containsGatekeeperPasswordHandle(data)) {
mFingerprintManager.generateChallenge((sensorId, challenge) -> {
mToken = BiometricUtils.requestGatekeeperHat(getActivity(), data,
mUserId, challenge);

View File

@@ -32,7 +32,9 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.biometrics.BiometricUtils;
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupChooseLockGeneric;
import com.android.settings.password.SetupSkipDialog;
@@ -58,29 +60,24 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
}
}
@Override
int getNegativeButtonTextId() {
return R.string.security_settings_face_enroll_introduction_cancel;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_LOCK_SCREEN_PRESENT, mAlreadyHadLockScreenSetup);
}
@Override
protected Intent getChooseLockIntent() {
Intent intent = new Intent(this, SetupChooseLockGeneric.class);
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
intent.putExtra(
LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
intent.putExtra(ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, true);
}
SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent;
}
@Override
protected Intent getEnrollingIntent() {
final Intent intent = new Intent(this, SetupFingerprintEnrollFindSensor.class);
if (BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
}
SetupWizardUtils.copySetupExtras(getIntent(), intent);
return intent;
}
@@ -157,6 +154,11 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
}
}
@Override
protected void onSkipButtonClick(View view) {
onCancelButtonClick(view);
}
/**
* Propagate lock screen metrics if the user goes back from the fingerprint setup screen
* after having added lock screen to his device.

View File

@@ -103,6 +103,7 @@ public class ChooseLockGeneric extends SettingsActivity {
private static final String TAG = "ChooseLockGenericFragment";
private static final String KEY_SKIP_FINGERPRINT = "unlock_skip_fingerprint";
private static final String KEY_SKIP_FACE = "unlock_skip_face";
private static final String KEY_SKIP_BIOMETRICS = "unlock_skip_biometrics";
private static final String PASSWORD_CONFIRMED = "password_confirmed";
private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
@@ -175,6 +176,7 @@ public class ChooseLockGeneric extends SettingsActivity {
protected boolean mForFingerprint = false;
protected boolean mForFace = false;
protected boolean mForBiometrics = false;
@Override
public int getMetricsCategory() {
@@ -216,6 +218,9 @@ public class ChooseLockGeneric extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
mRequestedMinComplexity = intent
.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
mCallerAppName =
@@ -303,17 +308,20 @@ public class ChooseLockGeneric extends SettingsActivity {
}
protected void addHeaderView() {
setHeaderView(R.layout.choose_lock_generic_biometric_header);
TextView textView = getHeaderView().findViewById(R.id.biometric_header_description);
if (mForFingerprint) {
setHeaderView(R.layout.choose_lock_generic_fingerprint_header);
if (mIsSetNewPassword) {
((TextView) getHeaderView().findViewById(R.id.fingerprint_header_description))
.setText(R.string.fingerprint_unlock_title);
textView.setText(R.string.fingerprint_unlock_title);
}
} else if (mForFace) {
setHeaderView(R.layout.choose_lock_generic_face_header);
if (mIsSetNewPassword) {
((TextView) getHeaderView().findViewById(R.id.face_header_description))
.setText(R.string.face_unlock_title);
textView.setText(R.string.face_unlock_title);
}
} else if (mForBiometrics) {
if (mIsSetNewPassword) {
textView.setText(R.string.biometrics_unlock_title);
}
}
}
@@ -328,7 +336,8 @@ public class ChooseLockGeneric extends SettingsActivity {
// unlock method to an insecure one
showFactoryResetProtectionWarningDialog(key);
return true;
} else if (KEY_SKIP_FINGERPRINT.equals(key) || KEY_SKIP_FACE.equals(key)) {
} else if (KEY_SKIP_FINGERPRINT.equals(key) || KEY_SKIP_FACE.equals(key)
|| KEY_SKIP_BIOMETRICS.equals(key)) {
Intent chooseLockGenericIntent = new Intent(getActivity(),
getInternalActivityClass());
chooseLockGenericIntent.setAction(getIntent().getAction());
@@ -357,7 +366,7 @@ public class ChooseLockGeneric extends SettingsActivity {
* @param disabled
*/
// TODO: why does this take disabled, its always called with a quality higher than
// what makes sense with disabled == true
// what makes sense with disabled == true
private void maybeEnableEncryption(int quality, boolean disabled) {
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
if (UserManager.get(getActivity()).isAdminUser()
@@ -381,8 +390,8 @@ public class ChooseLockGeneric extends SettingsActivity {
unlockMethodIntent);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT,
mForFingerprint);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE,
mForFace);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, mForFace);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, mForBiometrics);
// If the caller requested Gatekeeper Password to be returned, we assume it came
// from biometric enrollment. This should be cleaned up, since requesting
// Gatekeeper Password should not imply it came from biometric setup/settings.
@@ -531,6 +540,7 @@ public class ChooseLockGeneric extends SettingsActivity {
findPreference(ScreenLockType.NONE.preferenceKey).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_FINGERPRINT).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_FACE).setViewId(R.id.lock_none);
findPreference(KEY_SKIP_BIOMETRICS).setViewId(R.id.lock_none);
findPreference(ScreenLockType.PIN.preferenceKey).setViewId(R.id.lock_pin);
findPreference(ScreenLockType.PASSWORD.preferenceKey).setViewId(R.id.lock_password);
}
@@ -569,6 +579,12 @@ public class ChooseLockGeneric extends SettingsActivity {
setPreferenceTitle(ScreenLockType.PIN, R.string.face_unlock_set_unlock_pin);
setPreferenceTitle(ScreenLockType.PASSWORD,
R.string.face_unlock_set_unlock_password);
} else if (mForBiometrics) {
setPreferenceTitle(ScreenLockType.PATTERN,
R.string.biometrics_unlock_set_unlock_pattern);
setPreferenceTitle(ScreenLockType.PIN, R.string.biometrics_unlock_set_unlock_pin);
setPreferenceTitle(ScreenLockType.PASSWORD,
R.string.biometrics_unlock_set_unlock_password);
}
if (mManagedPasswordProvider.isSettingManagedPasswordSupported()) {
@@ -584,6 +600,9 @@ public class ChooseLockGeneric extends SettingsActivity {
if (!(mForFace && mIsSetNewPassword)) {
removePreference(KEY_SKIP_FACE);
}
if (!(mForBiometrics && mIsSetNewPassword)) {
removePreference(KEY_SKIP_BIOMETRICS);
}
}
private void setPreferenceTitle(ScreenLockType lock, @StringRes int title) {
@@ -727,6 +746,7 @@ public class ChooseLockGeneric extends SettingsActivity {
.setRequestedMinComplexity(mRequestedMinComplexity)
.setForFingerprint(mForFingerprint)
.setForFace(mForFace)
.setForBiometrics(mForBiometrics)
.setUserId(mUserId)
.setRequestGatekeeperPasswordHandle(mRequestGatekeeperPasswordHandle);
if (mUserPassword != null) {
@@ -743,6 +763,7 @@ public class ChooseLockGeneric extends SettingsActivity {
new ChooseLockPattern.IntentBuilder(getContext())
.setForFingerprint(mForFingerprint)
.setForFace(mForFace)
.setForBiometrics(mForBiometrics)
.setUserId(mUserId)
.setRequestGatekeeperPasswordHandle(mRequestGatekeeperPasswordHandle);
if (mUserPassword != null) {

View File

@@ -151,6 +151,11 @@ public class ChooseLockPassword extends SettingsActivity {
return this;
}
public IntentBuilder setForBiometrics(boolean forBiometrics) {
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
return this;
}
public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) {
mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level);
return this;
@@ -190,12 +195,16 @@ public class ChooseLockPassword extends SettingsActivity {
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
CharSequence msg = getText(R.string.lockpassword_choose_your_screen_lock_header);
if (forFingerprint) {
msg = getText(R.string.lockpassword_choose_your_password_header_for_fingerprint);
} else if (forFace) {
msg = getText(R.string.lockpassword_choose_your_password_header_for_face);
} else if (forBiometrics) {
msg = getText(R.string.lockpassword_choose_your_password_header_for_biometrics);
}
setTitle(msg);
@@ -232,6 +241,7 @@ public class ChooseLockPassword extends SettingsActivity {
private GlifLayout mLayout;
protected boolean mForFingerprint;
protected boolean mForFace;
protected boolean mForBiometrics;
private LockscreenCredential mFirstPassword;
private RecyclerView mPasswordRestrictionView;
@@ -254,9 +264,11 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_choose_your_screen_lock_header, // password
R.string.lockpassword_choose_your_password_header_for_fingerprint,
R.string.lockpassword_choose_your_password_header_for_face,
R.string.lockpassword_choose_your_password_header_for_biometrics,
R.string.lockpassword_choose_your_screen_lock_header, // pin
R.string.lockpassword_choose_your_pin_header_for_fingerprint,
R.string.lockpassword_choose_your_pin_header_for_face,
R.string.lockpassword_choose_your_pin_header_for_biometrics,
R.string.lockpassword_choose_your_password_message, // added security message
R.string.lock_settings_picker_biometrics_added_security_message,
R.string.lockpassword_choose_your_pin_message,
@@ -267,6 +279,8 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_password_header,
R.string.lockpassword_confirm_your_pin_header,
R.string.lockpassword_confirm_your_pin_header,
R.string.lockpassword_confirm_your_pin_header,
R.string.lockpassword_confirm_your_pin_header,
@@ -280,6 +294,8 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_passwords_dont_match,
R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_confirm_pins_dont_match,
R.string.lockpassword_confirm_pins_dont_match,
@@ -289,18 +305,22 @@ public class ChooseLockPassword extends SettingsActivity {
0,
R.string.lockpassword_confirm_label);
Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace,
int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace,
Stage(int hintInAlpha,
int hintInAlphaForFingerprint, int hintInAlphaForFace, int hintInAlphaForBiometrics,
int hintInNumeric,
int hintInNumericForFingerprint, int hintInNumericForFace, int hintInNumericForBiometrics,
int messageInAlpha, int messageInAlphaForBiometrics,
int messageInNumeric, int messageInNumericForBiometrics,
int nextButtonText) {
this.alphaHint = hintInAlpha;
this.alphaHintForFingerprint = hintInAlphaForFingerprint;
this.alphaHintForFace = hintInAlphaForFace;
this.alphaHintForBiometrics = hintInAlphaForBiometrics;
this.numericHint = hintInNumeric;
this.numericHintForFingerprint = hintInNumericForFingerprint;
this.numericHintForFace = hintInNumericForFace;
this.numericHintForBiometrics = hintInNumericForBiometrics;
this.alphaMessage = messageInAlpha;
this.alphaMessageForBiometrics = messageInAlphaForBiometrics;
@@ -312,16 +332,19 @@ public class ChooseLockPassword extends SettingsActivity {
public static final int TYPE_NONE = 0;
public static final int TYPE_FINGERPRINT = 1;
public static final int TYPE_FACE = 2;
public static final int TYPE_BIOMETRIC = 3;
// Password
public final int alphaHint;
public final int alphaHintForFingerprint;
public final int alphaHintForFace;
public final int alphaHintForBiometrics;
// PIN
public final int numericHint;
public final int numericHintForFingerprint;
public final int numericHintForFace;
public final int numericHintForBiometrics;
public final int alphaMessage;
public final int alphaMessageForBiometrics;
@@ -335,6 +358,8 @@ public class ChooseLockPassword extends SettingsActivity {
return alphaHintForFingerprint;
} else if (type == TYPE_FACE) {
return alphaHintForFace;
} else if (type == TYPE_BIOMETRIC) {
return alphaHintForBiometrics;
} else {
return alphaHint;
}
@@ -343,6 +368,8 @@ public class ChooseLockPassword extends SettingsActivity {
return numericHintForFingerprint;
} else if (type == TYPE_FACE) {
return numericHintForFace;
} else if (type == TYPE_BIOMETRIC) {
return numericHintForBiometrics;
} else {
return numericHint;
}
@@ -376,6 +403,8 @@ public class ChooseLockPassword extends SettingsActivity {
mForFingerprint = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
mMinComplexity = intent.getIntExtra(
EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
@@ -454,6 +483,8 @@ public class ChooseLockPassword extends SettingsActivity {
mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header));
} else if (mForFace) {
mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header));
} else if (mForBiometrics) {
mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
}
mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
@@ -546,9 +577,15 @@ public class ChooseLockPassword extends SettingsActivity {
}
protected int getStageType() {
return mForFingerprint ? Stage.TYPE_FINGERPRINT :
mForFace ? Stage.TYPE_FACE :
Stage.TYPE_NONE;
if (mForFingerprint) {
return Stage.TYPE_FINGERPRINT;
} else if (mForFace) {
return Stage.TYPE_FACE;
} else if (mForBiometrics) {
return Stage.TYPE_BIOMETRIC;
} else {
return Stage.TYPE_NONE;
}
}
private void setupPasswordRequirementsView(View view) {

View File

@@ -81,7 +81,7 @@ public class ChooseLockPattern extends SettingsActivity {
* behavior. So, now an activity does not finish itself until it gets this
* result.
*/
static final int RESULT_FINISHED = RESULT_FIRST_USER;
public static final int RESULT_FINISHED = RESULT_FIRST_USER;
private static final String TAG = "ChooseLockPattern";
@@ -134,6 +134,11 @@ public class ChooseLockPattern extends SettingsActivity {
return this;
}
public IntentBuilder setForBiometrics(boolean forBiometrics) {
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
return this;
}
/**
* Configures the launch such that at the end of the pattern enrollment, one of its
* managed profile (specified by {@code profileId}) will have its lockscreen unified
@@ -455,6 +460,7 @@ public class ChooseLockPattern extends SettingsActivity {
protected int mUserId;
protected boolean mForFingerprint;
protected boolean mForFace;
protected boolean mForBiometrics;
private static final String KEY_UI_STAGE = "uiStage";
private static final String KEY_PATTERN_CHOICE = "chosenPattern";
@@ -488,6 +494,8 @@ public class ChooseLockPattern extends SettingsActivity {
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
mForFace = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
}
@Override
@@ -506,6 +514,8 @@ public class ChooseLockPattern extends SettingsActivity {
layout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header));
} else if (mForFace) {
layout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header));
} else if (mForBiometrics) {
layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
}
}
@@ -732,8 +742,8 @@ public class ChooseLockPattern extends SettingsActivity {
} else {
mHeaderText.setText(stage.headerMessage);
}
final boolean forBiometrics = mForFingerprint || mForFace;
int message = forBiometrics ? stage.messageForBiometrics : stage.message;
final boolean forAnyBiometric = mForFingerprint || mForFace || mForBiometrics;
int message = forAnyBiometric ? stage.messageForBiometrics : stage.message;
if (message == ID_EMPTY_MESSAGE) {
mMessageText.setText("");
} else {
@@ -756,7 +766,7 @@ public class ChooseLockPattern extends SettingsActivity {
mHeaderText.setTextColor(mDefaultHeaderColorList);
}
if (stage == Stage.NeedToConfirm && forBiometrics) {
if (stage == Stage.NeedToConfirm && forAnyBiometric) {
mHeaderText.setText("");
mTitleText.setText(R.string.lockpassword_draw_your_pattern_again_header);
}

View File

@@ -50,8 +50,12 @@ public final class ChooseLockSettingsHelper {
public static final String EXTRA_KEY_FORCE_VERIFY = "force_verify";
// Gatekeeper HardwareAuthToken
public static final String EXTRA_KEY_CHALLENGE_TOKEN = "hw_auth_token";
// For the fingerprint-only path
public static final String EXTRA_KEY_FOR_FINGERPRINT = "for_fingerprint";
// For the face-only path
public static final String EXTRA_KEY_FOR_FACE = "for_face";
// For the paths where multiple biometric sensors exist
public static final String EXTRA_KEY_FOR_BIOMETRICS = "for_biometrics";
public static final String EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT = "for_cred_req_boot";
public static final String EXTRA_KEY_FOREGROUND_ONLY = "foreground_only";
public static final String EXTRA_KEY_REQUEST_GK_PW_HANDLE = "request_gk_pw_handle";

View File

@@ -111,28 +111,49 @@ final class SetNewPasswordController {
*/
public void dispatchSetNewPasswordIntent() {
final Bundle extras;
// TODO: handle the case with multiple biometrics, perhaps take an arg for biometric type?
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
&& mFaceManager != null
&& mFaceManager.isHardwareDetected()
&& !mFaceManager.hasEnrolledTemplates(mTargetUserId)
&& !isFaceDisabledByAdmin()) {
extras = getFaceChooseLockExtras();
} else if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
&& mFingerprintManager != null
final boolean hasFeatureFingerprint = mPackageManager
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
final boolean hasFeatureFace = mPackageManager
.hasSystemFeature(PackageManager.FEATURE_FACE);
final boolean shouldShowFingerprintEnroll = mFingerprintManager != null
&& mFingerprintManager.isHardwareDetected()
&& !mFingerprintManager.hasEnrolledFingerprints(mTargetUserId)
&& !isFingerprintDisabledByAdmin()) {
&& !isFingerprintDisabledByAdmin();
final boolean shouldShowFaceEnroll = mFaceManager != null
&& mFaceManager.isHardwareDetected()
&& !mFaceManager.hasEnrolledTemplates(mTargetUserId)
&& !isFaceDisabledByAdmin();
if (hasFeatureFace && shouldShowFaceEnroll
&& hasFeatureFingerprint && shouldShowFingerprintEnroll) {
extras = getBiometricChooseLockExtras();
} else if (hasFeatureFace && shouldShowFaceEnroll) {
extras = getFaceChooseLockExtras();
} else if (hasFeatureFingerprint && shouldShowFingerprintEnroll) {
extras = getFingerprintChooseLockExtras();
} else {
extras = new Bundle();
}
// No matter we show fingerprint options or not, we should tell the next activity which
// user is setting new password.
extras.putInt(Intent.EXTRA_USER_ID, mTargetUserId);
mUi.launchChooseLock(extras);
}
private Bundle getBiometricChooseLockExtras() {
Bundle chooseLockExtras = new Bundle();
chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
PASSWORD_QUALITY_SOMETHING);
chooseLockExtras.putBoolean(
ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS, true);
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true);
return chooseLockExtras;
}
private Bundle getFingerprintChooseLockExtras() {
Bundle chooseLockExtras = new Bundle();
chooseLockExtras.putInt(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,

View File

@@ -200,8 +200,9 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric {
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false),
/* isPatternMode= */ false,
/* isAlphaMode= */ false,
/* isFingerprintSupported= */ false,
/* isFaceSupported= */ false
/* forFingerprint= */ false,
/* forFace= */ false,
/* forBiometrics= */ false
);
dialog.show(getFragmentManager());
return true;
@@ -242,7 +243,7 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric {
}
private boolean isForBiometric() {
return mForFingerprint || mForFace;
return mForFingerprint || mForFace || mForBiometrics;
}
}

View File

@@ -104,18 +104,23 @@ public class SetupChooseLockPassword extends ChooseLockPassword {
@Override
protected void onSkipOrClearButtonClick(View view) {
if (mLeftButtonIsSkip) {
SetupSkipDialog dialog = SetupSkipDialog.newInstance(
getActivity().getIntent()
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false),
final Intent intent = getActivity().getIntent();
final boolean frpSupported = intent
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false);
final boolean forFingerprint = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
final SetupSkipDialog dialog = SetupSkipDialog.newInstance(
frpSupported,
/* isPatternMode= */ false,
mIsAlphaMode,
getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT,
false),
getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false)
);
forFingerprint,
forFace,
forBiometrics);
dialog.show(getFragmentManager());
return;
}

View File

@@ -90,18 +90,23 @@ public class SetupChooseLockPattern extends ChooseLockPattern {
@Override
protected void onSkipOrClearButtonClick(View view) {
if (mLeftButtonIsSkip) {
SetupSkipDialog dialog = SetupSkipDialog.newInstance(
getActivity().getIntent()
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false),
final Intent intent = getActivity().getIntent();
final boolean frpSupported = intent
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false);
final boolean forFingerprint = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
final boolean forFace = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
final boolean forBiometrics = intent
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
final SetupSkipDialog dialog = SetupSkipDialog.newInstance(
frpSupported,
/* isPatternMode= */ true,
/* isAlphaMode= */ false,
getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT,
false),
getActivity().getIntent()
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false)
);
forFingerprint,
forFace,
forBiometrics);
dialog.show(getFragmentManager());
return;
}

View File

@@ -43,14 +43,16 @@ public class SetupSkipDialog extends InstrumentedDialogFragment
public static final int RESULT_SKIP = Activity.RESULT_FIRST_USER + 10;
public static SetupSkipDialog newInstance(boolean isFrpSupported, boolean isPatternMode,
boolean isAlphanumericMode, boolean isFingerprintSupported, boolean isFaceSupported) {
boolean isAlphanumericMode, boolean forFingerprint, boolean forFace,
boolean forBiometrics) {
SetupSkipDialog dialog = new SetupSkipDialog();
Bundle args = new Bundle();
args.putBoolean(ARG_FRP_SUPPORTED, isFrpSupported);
args.putBoolean(ARG_LOCK_TYPE_PATTERN, isPatternMode);
args.putBoolean(ARG_LOCK_TYPE_ALPHANUMERIC, isAlphanumericMode);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, isFingerprintSupported);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, isFaceSupported);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace);
args.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
dialog.setArguments(args);
return dialog;
}
@@ -68,11 +70,13 @@ public class SetupSkipDialog extends InstrumentedDialogFragment
@NonNull
public AlertDialog.Builder onCreateDialogBuilder() {
Bundle args = getArguments();
final boolean isFaceSupported =
final boolean forFace =
args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE);
final boolean isFingerprintSupported =
final boolean forFingerprint =
args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT);
if (isFaceSupported || isFingerprintSupported) {
final boolean forBiometrics =
args.getBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS);
if (forFace || forFingerprint || forBiometrics) {
final int titleId;
if (args.getBoolean(ARG_LOCK_TYPE_PATTERN)) {
@@ -82,13 +86,20 @@ public class SetupSkipDialog extends InstrumentedDialogFragment
R.string.lock_screen_password_skip_title : R.string.lock_screen_pin_skip_title;
}
final int msgResId;
if (forBiometrics) {
msgResId = R.string.biometrics_lock_screen_setup_skip_dialog_text;
} else if (forFace) {
msgResId = R.string.face_lock_screen_setup_skip_dialog_text;
} else {
msgResId = R.string.fingerprint_lock_screen_setup_skip_dialog_text;
}
return new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.skip_lock_screen_dialog_button_label, this)
.setNegativeButton(R.string.cancel_lock_screen_dialog_button_label, this)
.setTitle(titleId)
.setMessage(isFaceSupported ?
R.string.face_lock_screen_setup_skip_dialog_text :
R.string.fingerprint_lock_screen_setup_skip_dialog_text);
.setMessage(msgResId);
} else {
return new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.skip_anyway_button_label, this)