Merge "Make BiometricPrompt honor max attempts before wipe" into rvc-dev
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3662877..ae38e34 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 0d9d426..13f3c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -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.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 @@
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 @@
@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 @@
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 @@
};
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 =