Merge "Make BiometricPrompt honor max attempts before wipe" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b51645d6fa
@@ -374,6 +374,17 @@
|
||||
<string name="biometric_dialog_wrong_password">Wrong password</string>
|
||||
<!-- Error string shown when the user enters too many incorrect attempts [CHAR LIMIT=120]-->
|
||||
<string name="biometric_dialog_credential_too_many_attempts">Too many incorrect attempts.\nTry again in <xliff:g id="number">%d</xliff:g> seconds.</string>
|
||||
<!-- Error string shown when the user enters an incorrect PIN/pattern/password and it counts towards the max attempts before the data on the device is wiped. [CHAR LIMIT=NONE]-->
|
||||
<string name="biometric_dialog_credential_attempts_before_wipe">Try again. Attempt <xliff:g id="attempts" example="1">%1$d</xliff:g> of <xliff:g id="max_attempts" example="3">%2$d</xliff:g>.</string>
|
||||
|
||||
<!-- Content of a dialog shown when the user has failed to provide the device lock too many times and the device is wiped. [CHAR LIMIT=NONE] -->
|
||||
<string name="biometric_dialog_failed_attempts_now_wiping_device">Too many incorrect attempts. This device\u2019s data will be deleted.</string>
|
||||
<!-- Content of a dialog shown when the user has failed to provide the user lock too many times and the user is removed. [CHAR LIMIT=NONE] -->
|
||||
<string name="biometric_dialog_failed_attempts_now_wiping_user">Too many incorrect attempts. This user will be deleted.</string>
|
||||
<!-- Content of a dialog shown when the user has failed to provide the work lock too many times and the work profile is removed. [CHAR LIMIT=NONE] -->
|
||||
<string name="biometric_dialog_failed_attempts_now_wiping_profile">Too many incorrect attempts. This work profile and its data will be deleted.</string>
|
||||
<!-- Button label to dismiss the dialog telling the user the device, user, or work profile has been wiped. [CHAR LIMIT=40] -->
|
||||
<string name="biometric_dialog_now_wiping_dialog_dismiss">Dismiss</string>
|
||||
|
||||
<!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
|
||||
<string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.os.AsyncTask;
|
||||
@@ -26,32 +28,49 @@ import android.os.CountDownTimer;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Abstract base class for Pin, Pattern, or Password authentication, for
|
||||
* {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}}
|
||||
*/
|
||||
public abstract class AuthCredentialView extends LinearLayout {
|
||||
|
||||
private static final String TAG = "BiometricPrompt/AuthCredentialView";
|
||||
private static final int ERROR_DURATION_MS = 3000;
|
||||
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
static final int USER_TYPE_PRIMARY = 1;
|
||||
static final int USER_TYPE_MANAGED_PROFILE = 2;
|
||||
static final int USER_TYPE_SECONDARY = 3;
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({USER_TYPE_PRIMARY, USER_TYPE_MANAGED_PROFILE, USER_TYPE_SECONDARY})
|
||||
private @interface UserType {}
|
||||
|
||||
protected final Handler mHandler;
|
||||
protected final LockPatternUtils mLockPatternUtils;
|
||||
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final UserManager mUserManager;
|
||||
private final DevicePolicyManager mDevicePolicyManager;
|
||||
|
||||
private Bundle mBiometricPromptBundle;
|
||||
private AuthPanelController mPanelController;
|
||||
@@ -65,7 +84,6 @@ public abstract class AuthCredentialView extends LinearLayout {
|
||||
protected TextView mErrorView;
|
||||
|
||||
protected @Utils.CredentialType int mCredentialType;
|
||||
protected final LockPatternUtils mLockPatternUtils;
|
||||
protected AuthContainerView mContainerView;
|
||||
protected Callback mCallback;
|
||||
protected AsyncTask<?, ?, ?> mPendingLockCheck;
|
||||
@@ -106,14 +124,18 @@ public abstract class AuthCredentialView extends LinearLayout {
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
mErrorView.setText("");
|
||||
if (mErrorView != null) {
|
||||
mErrorView.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Runnable mClearErrorRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mErrorView.setText("");
|
||||
if (mErrorView != null) {
|
||||
mErrorView.setText("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,12 +145,18 @@ public abstract class AuthCredentialView extends LinearLayout {
|
||||
mLockPatternUtils = new LockPatternUtils(mContext);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
|
||||
mUserManager = mContext.getSystemService(UserManager.class);
|
||||
mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
|
||||
}
|
||||
|
||||
protected void showError(String error) {
|
||||
mHandler.removeCallbacks(mClearErrorRunnable);
|
||||
mErrorView.setText(error);
|
||||
mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
|
||||
if (mHandler != null) {
|
||||
mHandler.removeCallbacks(mClearErrorRunnable);
|
||||
mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
|
||||
}
|
||||
if (mErrorView != null) {
|
||||
mErrorView.setText(error);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTextOrHide(TextView view, CharSequence text) {
|
||||
@@ -274,26 +302,94 @@ public abstract class AuthCredentialView extends LinearLayout {
|
||||
};
|
||||
mErrorTimer.start();
|
||||
} else {
|
||||
final int error;
|
||||
switch (mCredentialType) {
|
||||
case Utils.CREDENTIAL_PIN:
|
||||
error = R.string.biometric_dialog_wrong_pin;
|
||||
break;
|
||||
case Utils.CREDENTIAL_PATTERN:
|
||||
error = R.string.biometric_dialog_wrong_pattern;
|
||||
break;
|
||||
case Utils.CREDENTIAL_PASSWORD:
|
||||
error = R.string.biometric_dialog_wrong_password;
|
||||
break;
|
||||
default:
|
||||
error = R.string.biometric_dialog_wrong_password;
|
||||
break;
|
||||
final boolean didUpdateErrorText = reportFailedAttempt();
|
||||
if (!didUpdateErrorText) {
|
||||
final @StringRes int errorRes;
|
||||
switch (mCredentialType) {
|
||||
case Utils.CREDENTIAL_PIN:
|
||||
errorRes = R.string.biometric_dialog_wrong_pin;
|
||||
break;
|
||||
case Utils.CREDENTIAL_PATTERN:
|
||||
errorRes = R.string.biometric_dialog_wrong_pattern;
|
||||
break;
|
||||
case Utils.CREDENTIAL_PASSWORD:
|
||||
default:
|
||||
errorRes = R.string.biometric_dialog_wrong_password;
|
||||
break;
|
||||
}
|
||||
showError(getResources().getString(errorRes));
|
||||
}
|
||||
showError(getResources().getString(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean reportFailedAttempt() {
|
||||
boolean result = updateErrorMessage(
|
||||
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
|
||||
mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean updateErrorMessage(int numAttempts) {
|
||||
// Don't show any message if there's no maximum number of attempts.
|
||||
final int maxAttempts = mLockPatternUtils.getMaximumFailedPasswordsForWipe(
|
||||
mEffectiveUserId);
|
||||
if (maxAttempts <= 0 || numAttempts <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the on-screen error string.
|
||||
if (mErrorView != null) {
|
||||
final String message = getResources().getString(
|
||||
R.string.biometric_dialog_credential_attempts_before_wipe,
|
||||
numAttempts,
|
||||
maxAttempts);
|
||||
showError(message);
|
||||
}
|
||||
|
||||
// Only show popup dialog before wipe.
|
||||
final int remainingAttempts = maxAttempts - numAttempts;
|
||||
if (remainingAttempts <= 0) {
|
||||
showNowWipingMessage();
|
||||
mContainerView.animateAway(AuthDialogCallback.DISMISSED_ERROR);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showNowWipingMessage() {
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
|
||||
.setMessage(getNowWipingMessageRes(getUserTypeForWipe()))
|
||||
.setPositiveButton(R.string.biometric_dialog_now_wiping_dialog_dismiss, null)
|
||||
.create();
|
||||
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private @UserType int getUserTypeForWipe() {
|
||||
final UserInfo userToBeWiped = mUserManager.getUserInfo(
|
||||
mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
|
||||
if (userToBeWiped == null || userToBeWiped.isPrimary()) {
|
||||
return USER_TYPE_PRIMARY;
|
||||
} else if (userToBeWiped.isManagedProfile()) {
|
||||
return USER_TYPE_MANAGED_PROFILE;
|
||||
} else {
|
||||
return USER_TYPE_SECONDARY;
|
||||
}
|
||||
}
|
||||
|
||||
private static @StringRes int getNowWipingMessageRes(@UserType int userType) {
|
||||
switch (userType) {
|
||||
case USER_TYPE_PRIMARY:
|
||||
return R.string.biometric_dialog_failed_attempts_now_wiping_device;
|
||||
case USER_TYPE_MANAGED_PROFILE:
|
||||
return R.string.biometric_dialog_failed_attempts_now_wiping_profile;
|
||||
case USER_TYPE_SECONDARY:
|
||||
return R.string.biometric_dialog_failed_attempts_now_wiping_user;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized user type:" + userType);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static CharSequence getTitle(@NonNull Bundle bundle) {
|
||||
final CharSequence credentialTitle =
|
||||
|
||||
Reference in New Issue
Block a user