Merge "Fix 5026428: Rework unlock attempt logic with active DPM to show better messages"
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 2548eb5..27fa8d4 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;