import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import java.util.concurrent.Executor;
*/
public class BiometricFragment extends InstrumentedFragment {
- private static final String KEY_TITLE = "title";
- private static final String KEY_SUBTITLE = "subtitle";
- private static final String KEY_DESCRIPTION = "description";
- private static final String KEY_NEGATIVE_TEXT = "negative_text";
-
// Re-set by the application. Should be done upon orientation changes, etc
private Executor mClientExecutor;
private AuthenticationCallback mClientCallback;
// Created/Initialized once and retained
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private PromptInfo mPromptInfo;
+ private Bundle mBundle;
private BiometricPrompt mBiometricPrompt;
private CancellationSignal mCancellationSignal;
public void onClick(DialogInterface dialog, int which) {
mAuthenticationCallback.onAuthenticationError(
BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON,
- mPromptInfo.getNegativeButtonText());
+ mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT));
}
};
- public static BiometricFragment newInstance(PromptInfo info) {
+ /**
+ * @param bundle Bundle passed from {@link BiometricPrompt.Builder#buildIntent()}
+ * @return
+ */
+ public static BiometricFragment newInstance(Bundle bundle) {
BiometricFragment biometricFragment = new BiometricFragment();
- biometricFragment.setArguments(info.getBundle());
+ biometricFragment.setArguments(bundle);
return biometricFragment;
}
super.onCreate(savedInstanceState);
setRetainInstance(true);
- mPromptInfo = new PromptInfo(getArguments());
+ mBundle = getArguments();
mBiometricPrompt = new BiometricPrompt.Builder(getContext())
- .setTitle(mPromptInfo.getTitle())
+ .setTitle(mBundle.getString(BiometricPrompt.KEY_TITLE))
.setUseDefaultTitle() // use default title if title is null/empty
- .setSubtitle(mPromptInfo.getSubtitle())
- .setDescription(mPromptInfo.getDescription())
- .setNegativeButton(mPromptInfo.getNegativeButtonText(), mClientExecutor,
- mNegativeButtonListener)
+ .setSubtitle(mBundle.getString(BiometricPrompt.KEY_SUBTITLE))
+ .setDescription(mBundle.getString(BiometricPrompt.KEY_DESCRIPTION))
+ .setRequireConfirmation(mBundle.getBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION))
+ .setNegativeButton(getResources().getString(
+ R.string.confirm_device_credential_use_alternate_method),
+ mClientExecutor, mNegativeButtonListener)
.build();
mCancellationSignal = new CancellationSignal();
public int getMetricsCategory() {
return SettingsEnums.BIOMETRIC_FRAGMENT;
}
-
- /**
- * A simple wrapper for BiometricPrompt.PromptInfo. Since we want to manage the lifecycle
- * of BiometricPrompt correctly, the information needs to be stored in here.
- */
- static class PromptInfo {
- private final Bundle mBundle;
-
- private PromptInfo(Bundle bundle) {
- mBundle = bundle;
- }
-
- Bundle getBundle() {
- return mBundle;
- }
-
- public CharSequence getTitle() {
- return mBundle.getCharSequence(KEY_TITLE);
- }
-
- public CharSequence getSubtitle() {
- return mBundle.getCharSequence(KEY_SUBTITLE);
- }
-
- public CharSequence getDescription() {
- return mBundle.getCharSequence(KEY_DESCRIPTION);
- }
-
- public CharSequence getNegativeButtonText() {
- return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
- }
-
- public static class Builder {
- private final Bundle mBundle = new Bundle();
-
- public Builder setTitle(@NonNull CharSequence title) {
- mBundle.putCharSequence(KEY_TITLE, title);
- return this;
- }
-
- public Builder setSubtitle(@Nullable CharSequence subtitle) {
- mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
- return this;
- }
-
- public Builder setDescription(@Nullable CharSequence description) {
- mBundle.putCharSequence(KEY_DESCRIPTION, description);
- return this;
- }
-
- public Builder setNegativeButtonText(@NonNull CharSequence text) {
- mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
- return this;
- }
-
- public PromptInfo build() {
- return new PromptInfo(mBundle);
- }
- }
- }
}
import android.app.trust.TrustManager;
import android.content.Context;
import android.content.Intent;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
private TrustManager mTrustManager;
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private Handler mHandler = new Handler(Looper.getMainLooper());
+ private boolean mIsFallback; // BiometricPrompt fallback
+ private boolean mCCLaunched;
private String mTitle;
private String mDetails;
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
if (!mGoingToBackground) {
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
+ if (mIsFallback) {
+ mBiometricManager.onConfirmDeviceCredentialError(
+ BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
+ getString(
+ com.android.internal.R.string
+ .biometric_error_user_canceled));
+ }
finish();
} else {
// All other errors go to some version of CC
ConfirmDeviceCredentialUtils.checkForPendingIntent(
ConfirmDeviceCredentialActivity.this);
+ if (mIsFallback) {
+ mBiometricManager.onConfirmDeviceCredentialSuccess();
+ }
+
setResult(Activity.RESULT_OK);
finish();
}
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this);
final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
+ Bundle bpBundle =
+ intent.getBundleExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE);
+ if (bpBundle != null) {
+ mIsFallback = true;
+ // TODO: CDC maybe should show description as well.
+ mTitle = bpBundle.getString(BiometricPrompt.KEY_TITLE);
+ mDetails = bpBundle.getString(BiometricPrompt.KEY_SUBTITLE);
+ } else {
+ bpBundle = new Bundle();
+ bpBundle.putString(BiometricPrompt.KEY_TITLE, mTitle);
+ bpBundle.putString(BiometricPrompt.KEY_SUBTITLE, mDetails);
+ }
+
boolean launchedBiometric = false;
boolean launchedCDC = false;
// If the target is a managed user and user key not unlocked yet, we will force unlock
&& !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
mCredentialMode = CREDENTIAL_MANAGED;
if (isBiometricAllowed(effectiveUserId)) {
- showBiometricPrompt();
+ showBiometricPrompt(bpBundle);
launchedBiometric = true;
} else {
showConfirmCredentials();
if (isBiometricAllowed(effectiveUserId)) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
- showBiometricPrompt();
+ showBiometricPrompt(bpBundle);
launchedBiometric = true;
} else {
showConfirmCredentials();
if (mBiometricFragment != null) {
mBiometricFragment.cancel();
}
+
+ if (mIsFallback && !mCCLaunched) {
+ mBiometricManager.onConfirmDeviceCredentialError(
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+ getString(com.android.internal.R.string.biometric_error_user_canceled));
+ }
+
finish();
} else {
mGoingToBackground = false;
&& !isBiometricDisabledByAdmin(effectiveUserId);
}
- private void showBiometricPrompt() {
+ private void showBiometricPrompt(Bundle bundle) {
mBiometricManager.setActiveUser(mUserId);
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
boolean newFragment = false;
if (mBiometricFragment == null) {
- final BiometricFragment.PromptInfo info = new BiometricFragment.PromptInfo.Builder()
- .setTitle(mTitle)
- .setSubtitle(mDetails)
- .setNegativeButtonText(getResources()
- .getString(R.string.confirm_device_credential_use_alternate_method))
- .build();
- mBiometricFragment = BiometricFragment.newInstance(info);
+ mBiometricFragment = BiometricFragment.newInstance(bundle);
newFragment = true;
}
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
* Shows ConfirmDeviceCredentials for normal apps.
*/
private void showConfirmCredentials() {
+ mCCLaunched = true;
boolean launched = false;
if (mCredentialMode == CREDENTIAL_MANAGED) {
// We set the challenge as 0L, so it will force to unlock managed profile when it
package com.android.settings.password;
import android.app.KeyguardManager;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.os.Bundle;
import android.os.UserManager;
import android.view.MenuItem;
private boolean mFirstTimeVisible = true;
private boolean mIsKeyguardLocked = false;
private ConfirmCredentialTheme mConfirmCredentialTheme;
+ private BiometricManager mBiometricManager;
private boolean isInternalActivity() {
return (this instanceof ConfirmLockPassword.InternalActivity)
}
super.onCreate(savedState);
+ mBiometricManager = getSystemService(BiometricManager.class);
+
if (mConfirmCredentialTheme == ConfirmCredentialTheme.NORMAL) {
// Prevent the content parent from consuming the window insets because GlifLayout uses
// it to show the status bar background.
}
@Override
+ public void onStop() {
+ super.onStop();
+ // TODO(b/123378871): Remove when moved.
+ if (!isChangingConfigurations()) {
+ mBiometricManager.onConfirmDeviceCredentialError(
+ BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
+ getString(com.android.internal.R.string.biometric_error_user_canceled));
+ }
+ }
+
+ @Override
public void finish() {
super.finish();
if (getIntent().getBooleanExtra(
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserManager;
protected final Handler mHandler = new Handler();
protected boolean mFrp;
private CharSequence mFrpAlternateButtonText;
+ protected BiometricManager mBiometricManager;
private boolean isInternalActivity() {
return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
mLockPatternUtils = new LockPatternUtils(getActivity());
mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
Context.DEVICE_POLICY_SERVICE);
+ mBiometricManager = getActivity().getSystemService(BiometricManager.class);
}
@Override
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
mUserManager, mEffectiveUserId);
}
+ mBiometricManager.onConfirmDeviceCredentialSuccess();
startDisappearAnimation(intent);
ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
} else {
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
mUserManager, mEffectiveUserId);
}
+ mBiometricManager.onConfirmDeviceCredentialSuccess();
startDisappearAnimation(intent);
ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
} else {