diff options
Diffstat (limited to 'src/com/android/settings')
23 files changed, 956 insertions, 196 deletions
diff --git a/src/com/android/settings/EncryptionInterstitial.java b/src/com/android/settings/EncryptionInterstitial.java index 7a11053c57..c65cda7bda 100644 --- a/src/com/android/settings/EncryptionInterstitial.java +++ b/src/com/android/settings/EncryptionInterstitial.java @@ -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; } diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index 35a4546953..f6f44a8386 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -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; diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java index ecb2192572..1d9d07bf1d 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollBase.java +++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java @@ -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; diff --git a/src/com/android/settings/biometrics/BiometricEnrollCheckbox.java b/src/com/android/settings/biometrics/BiometricEnrollCheckbox.java new file mode 100644 index 0000000000..d2ff634942 --- /dev/null +++ b/src/com/android/settings/biometrics/BiometricEnrollCheckbox.java @@ -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); + } +} diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index 8cd5a70704..26df474aa5 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -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); } diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java index e458f03b94..d93a321cac 100644 --- a/src/com/android/settings/biometrics/BiometricUtils.java +++ b/src/com/android/settings/biometrics/BiometricUtils.java @@ -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; } } diff --git a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java new file mode 100644 index 0000000000..c1e906a8d4 --- /dev/null +++ b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java @@ -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(); + } + } +} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java index 4c4fa1174f..3e9fba281a 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java @@ -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); diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index 59d1a604e8..55a56ed6a7 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -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"; @@ -45,6 +53,20 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { 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 { diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java index 725123048e..5c8447fd74 100644 --- a/src/com/android/settings/biometrics/face/FaceSettings.java +++ b/src/com/android/settings/biometrics/face/FaceSettings.java @@ -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(); } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 8029c7dfcc..c320ef5a8d 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -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); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index 78bb9788b9..dbbcef1156 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -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 diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index a65316940c..052b3f0057 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -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); diff --git a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java index 7df66b789f..c8a4c05a93 100644 --- a/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/SetupFingerprintEnrollIntroduction.java @@ -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; @@ -59,28 +61,23 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu } @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_LOCK_SCREEN_PRESENT, mAlreadyHadLockScreenSetup); + int getNegativeButtonTextId() { + return R.string.security_settings_face_enroll_introduction_cancel; } @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; + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_LOCK_SCREEN_PRESENT, mAlreadyHadLockScreenSetup); } @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. diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 5b3013da21..35e369e942 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -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) { diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index 6cfb1efb51..e393af5951 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -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) { diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index 7ed9e6bcbe..ff9bf1a6aa 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -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); } diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index 1d10408e4f..1e1f59fddf 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -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"; diff --git a/src/com/android/settings/password/SetNewPasswordController.java b/src/com/android/settings/password/SetNewPasswordController.java index 393e8c76bc..03efa605d5 100644 --- a/src/com/android/settings/password/SetNewPasswordController.java +++ b/src/com/android/settings/password/SetNewPasswordController.java @@ -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 + + 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(); + final boolean shouldShowFaceEnroll = mFaceManager != null && mFaceManager.isHardwareDetected() && !mFaceManager.hasEnrolledTemplates(mTargetUserId) - && !isFaceDisabledByAdmin()) { + && !isFaceDisabledByAdmin(); + + if (hasFeatureFace && shouldShowFaceEnroll + && hasFeatureFingerprint && shouldShowFingerprintEnroll) { + extras = getBiometricChooseLockExtras(); + } else if (hasFeatureFace && shouldShowFaceEnroll) { extras = getFaceChooseLockExtras(); - } else if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) - && mFingerprintManager != null - && mFingerprintManager.isHardwareDetected() - && !mFingerprintManager.hasEnrolledFingerprints(mTargetUserId) - && !isFingerprintDisabledByAdmin()) { + } 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, diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java index aa17f8e987..5a0a2a6e5b 100644 --- a/src/com/android/settings/password/SetupChooseLockGeneric.java +++ b/src/com/android/settings/password/SetupChooseLockGeneric.java @@ -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; } } diff --git a/src/com/android/settings/password/SetupChooseLockPassword.java b/src/com/android/settings/password/SetupChooseLockPassword.java index 60bb0cdc30..e82dc94e12 100644 --- a/src/com/android/settings/password/SetupChooseLockPassword.java +++ b/src/com/android/settings/password/SetupChooseLockPassword.java @@ -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; } diff --git a/src/com/android/settings/password/SetupChooseLockPattern.java b/src/com/android/settings/password/SetupChooseLockPattern.java index 8e59d8c582..e80599f3fa 100644 --- a/src/com/android/settings/password/SetupChooseLockPattern.java +++ b/src/com/android/settings/password/SetupChooseLockPattern.java @@ -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; } diff --git a/src/com/android/settings/password/SetupSkipDialog.java b/src/com/android/settings/password/SetupSkipDialog.java index 68f8dd4888..ef00357ae8 100644 --- a/src/com/android/settings/password/SetupSkipDialog.java +++ b/src/com/android/settings/password/SetupSkipDialog.java @@ -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) |