Fix 5026428: Rework unlock attempt logic with active DPM to show better messages
Prior to this change, we didn't have a mechanism to warn the user when
they were approaching the wipe data threshold dictated by active DPMs.
Here's the new flow:
- If a device policy manager is installed and sets the max
password attempts, we start warning the user when they are within
a grace period of hitting the max (currently hard-wired to 5).
- We continue to show a dialog after each continued attempt
until the user reaches 0 remaining attempts.
- We now show a message when they hit 0 so they know why their device is
being reset. The device will reboot and wipe data shortly after this final
dialog is shown.
Also increased the criteria for a pattern attempt from 3 dots to 4 dots
since the user can never set a pattern less than 4 in length. This will
greatly reduce the likelihood of a false wipe on the pattern unlock screens
without compromising security.
Change-Id: I28825ef21dfa2e2b6540e743252c6d50c41e5ad7
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index e4322c6..804f28a 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -83,6 +83,13 @@
*/
public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L;
+
+ /**
+ * This dictates when we start telling the user that continued failed attempts will wipe
+ * their device.
+ */
+ public static final int FAILED_ATTEMPTS_BEFORE_WIPE_GRACE = 5;
+
/**
* The minimum number of dots in a valid pattern.
*/
@@ -93,7 +100,7 @@
* attempt for it to be counted against the counts that affect
* {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
*/
- public static final int MIN_PATTERN_REGISTER_FAIL = 3;
+ public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
@@ -112,6 +119,7 @@
private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean(false);
private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean(false);
+
private static FileObserver sPasswordObserver;
private static class PasswordFileObserver extends FileObserver {
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6aff54e..1f6ab6f5 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1300,8 +1300,8 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_writeApnSettings">change/intercept network settings and traffic</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeApnSettings">Allows an application to change network settings and to intercept and inspect all network traffic,
- for example to change the proxy and port of any APN. Malicious applications could monitor, redirect, or modify network
+ <string name="permdesc_writeApnSettings">Allows an application to change network settings and to intercept and inspect all network traffic,
+ for example to change the proxy and port of any APN. Malicious applications could monitor, redirect, or modify network
packets without your knowledge.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1857,7 +1857,7 @@
\n\nPlease try again in <xliff:g id="number">%d</xliff:g> seconds.
</string>
- <!-- For the unlock screen, Information message shown in dialog when user is almost at the limit
+ <!-- For the unlock screen, informational message shown in dialog when user is almost at the limit
where they will be locked out and may have to enter an alternate username/password to unlock the phone -->
<string name="lockscreen_failed_attempts_almost_glogin" product="tablet">
You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
@@ -1865,7 +1865,8 @@
you will be asked to unlock your tablet using your Google sign-in.\n\n
Please try again in <xliff:g id="number">%d</xliff:g> seconds.
</string>
- <!-- For the unlock screen, Information message shown in dialog when user is almost at the limit
+
+ <!-- For the unlock screen, informational message shown in dialog when user is almost at the limit
where they will be locked out and may have to enter an alternate username/password to unlock the phone -->
<string name="lockscreen_failed_attempts_almost_glogin" product="default">
You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
@@ -1874,6 +1875,36 @@
Please try again in <xliff:g id="number">%d</xliff:g> seconds.
</string>
+ <!-- For the unlock screen, informational message shown in dialog when user is almost at the limit
+ where the device will be wiped. -->
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet">
+ You have incorrectly attempted to unlock the tablet <xliff:g id="number">%d</xliff:g> times.
+ After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+ the tablet will be reset to factory default and all user data will be lost.
+ </string>
+
+ <!-- For the unlock screen, informational message shown in dialog when user is almost at the limit
+ where the device will be wiped. -->
+ <string name="lockscreen_failed_attempts_almost_at_wipe" product="default">
+ You have incorrectly attempted to unlock the phone <xliff:g id="number">%d</xliff:g> times.
+ After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+ the phone will be reset to factory default and all user data will be lost.
+ </string>
+
+ <!-- For the unlock screen, informational message shown in dialog when user has exceeded the
+ maximum attempts and the device will now be wiped -->
+ <string name="lockscreen_failed_attempts_now_wiping" product="tablet">
+ You have incorrectly attempted to unlock the tablet <xliff:g id="number">%d</xliff:g> times.
+ The tablet will now be reset to factory default.
+ </string>
+
+ <!-- For the unlock screen, informational message shown in dialog when user has exceeded the
+ maximum attempts and the device will now be wiped -->
+ <string name="lockscreen_failed_attempts_now_wiping" product="default">
+ You have incorrectly attempted to unlock the phone <xliff:g id="number">%d</xliff:g> times.
+ The phone will now be reset to factory default.
+ </string>
+
<!-- On the unlock screen, countdown message shown while user is waiting to try again after too many
failed attempts -->
<string name="lockscreen_too_many_failed_attempts_countdown">Try again in <xliff:g id="number">%d</xliff:g> seconds.</string>
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index b60bae7..adcc9c0 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -42,6 +42,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
@@ -294,22 +295,47 @@
public void reportFailedUnlockAttempt() {
mUpdateMonitor.reportFailedAttempt();
final int failedAttempts = mUpdateMonitor.getFailedAttempts();
- if (DEBUG) Log.d(TAG,
- "reportFailedPatternAttempt: #" + failedAttempts +
+ if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts +
" (enableFallback=" + mEnableFallback + ")");
- final boolean usingLockPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality()
+
+ final boolean usingPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality()
== DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- if (usingLockPattern && mEnableFallback && failedAttempts ==
- (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
- - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
- showAlmostAtAccountLoginDialog();
- } else if (usingLockPattern && mEnableFallback
- && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
- mLockPatternUtils.setPermanentlyLocked(true);
- updateScreen(mMode);
- } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)
- == 0) {
- showTimeoutDialog();
+
+ final int failedAttemptsBeforeWipe = mLockPatternUtils.getDevicePolicyManager()
+ .getMaximumFailedPasswordsForWipe(null);
+
+ final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
+ - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+
+ final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
+ (failedAttemptsBeforeWipe - failedAttempts)
+ : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
+
+ if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
+ // If we reach this code, it means the user has installed a DevicePolicyManager
+ // that requests device wipe after N attempts. Once we get below the grace
+ // period, we'll post this dialog every time as a clear warning until the
+ // bombshell hits and the device is wiped.
+ if (remainingBeforeWipe > 0) {
+ showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe);
+ } else {
+ // Too many attempts. The device will be wiped shortly.
+ Slog.i(TAG, "Too many unlock attempts; device will be wiped!");
+ showWipeDialog(failedAttempts);
+ }
+ } else if (usingPattern && mEnableFallback) {
+ if (failedAttempts == failedAttemptWarning) {
+ showAlmostAtAccountLoginDialog();
+ } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
+ mLockPatternUtils.setPermanentlyLocked(true);
+ updateScreen(mMode);
+ }
+ } else {
+ final boolean showTimeout =
+ (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0;
+ if (showTimeout) {
+ showTimeoutDialog();
+ }
}
mLockPatternUtils.reportFailedPasswordAttempt();
}
@@ -727,26 +753,12 @@
return currentMode;
}
- private void showTimeoutDialog() {
- int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
- int messageId = R.string.lockscreen_too_many_failed_attempts_dialog_message;
- if(getUnlockMode() == UnlockMode.Password) {
- if(mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
- messageId = R.string.lockscreen_too_many_failed_pin_attempts_dialog_message;
- } else {
- messageId = R.string.lockscreen_too_many_failed_password_attempts_dialog_message;
- }
- }
- String message = mContext.getString(
- messageId,
- mUpdateMonitor.getFailedAttempts(),
- timeoutInSeconds);
+ private void showDialog(String title, String message) {
final AlertDialog dialog = new AlertDialog.Builder(mContext)
- .setTitle(null)
- .setMessage(message)
- .setNeutralButton(R.string.ok, null)
- .create();
+ .setTitle(title)
+ .setMessage(message)
+ .setNeutralButton(R.string.ok, null)
+ .create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
if (!mContext.getResources().getBoolean(
com.android.internal.R.bool.config_sf_slowBlur)) {
@@ -757,27 +769,42 @@
dialog.show();
}
+ private void showTimeoutDialog() {
+ int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+ int messageId = R.string.lockscreen_too_many_failed_attempts_dialog_message;
+ if (getUnlockMode() == UnlockMode.Password) {
+ if(mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
+ messageId = R.string.lockscreen_too_many_failed_pin_attempts_dialog_message;
+ } else {
+ messageId = R.string.lockscreen_too_many_failed_password_attempts_dialog_message;
+ }
+ }
+ String message = mContext.getString(messageId, mUpdateMonitor.getFailedAttempts(),
+ timeoutInSeconds);
+ showDialog(null, message);
+ }
+
private void showAlmostAtAccountLoginDialog() {
+ final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+ final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
+ - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+ String message = mContext.getString(R.string.lockscreen_failed_attempts_almost_glogin,
+ count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds);
+ showDialog(null, message);
+ }
+
+ private void showAlmostAtWipeDialog(int attempts, int remaining) {
int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
String message = mContext.getString(
- R.string.lockscreen_failed_attempts_almost_glogin,
- LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
- - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
- LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
- timeoutInSeconds);
- final AlertDialog dialog = new AlertDialog.Builder(mContext)
- .setTitle(null)
- .setMessage(message)
- .setNeutralButton(R.string.ok, null)
- .create();
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- if (!mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_sf_slowBlur)) {
- dialog.getWindow().setFlags(
- WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
- WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
- }
- dialog.show();
+ R.string.lockscreen_failed_attempts_almost_at_wipe, attempts, remaining);
+ showDialog(null, message);
+ }
+
+ private void showWipeDialog(int attempts) {
+ String message = mContext.getString(
+ R.string.lockscreen_failed_attempts_now_wiping, attempts);
+ showDialog(null, message);
}
/**
diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
index d70b3bb..ee0a6e9 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -41,7 +41,6 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;