Merge "Make BiometricPrompt honor max attempts before wipe" into rvc-dev

This commit is contained in:
Curtis Belmonte
2020-03-20 00:10:28 +00:00
committed by Android (Google) Code Review
2 changed files with 131 additions and 24 deletions

View File

@@ -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>

View File

@@ -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 =