Merge "Lots of keyguard improvements - Fix "too many attempts" dialogs - Fix account unlock mechanism so the user can use email account as backup for pattern unlock - Add mechanism to support future account recovery from non-pattern screen - Tune animation timing for flipping security view. - Move password field to the top of the security view - Add padding and visual feedback to navigation area button" into jb-mr1-dev
diff --git a/core/res/res/anim/keyguard_security_animate_in.xml b/core/res/res/anim/keyguard_security_animate_in.xml
index 6e1e17a..4ee30c3 100644
--- a/core/res/res/anim/keyguard_security_animate_in.xml
+++ b/core/res/res/anim/keyguard_security_animate_in.xml
@@ -26,8 +26,8 @@
         android:pivotY="50%"
         android:fillEnabled="true"
         android:fillAfter="true"
-        android:duration="@integer/flip_duration"
-        android:startOffset="@integer/flip_duration" />
+        android:duration="@integer/kg_security_flip_duration"
+        android:startOffset="@integer/kg_security_flip_duration" />
 
 </set>
 
diff --git a/core/res/res/anim/keyguard_security_animate_out.xml b/core/res/res/anim/keyguard_security_animate_out.xml
index 5d65cd0..76d065c 100644
--- a/core/res/res/anim/keyguard_security_animate_out.xml
+++ b/core/res/res/anim/keyguard_security_animate_out.xml
@@ -26,7 +26,7 @@
         android:pivotY="50%"
         android:fillEnabled="true"
         android:fillAfter="true"
-        android:duration="@integer/flip_duration" />
+        android:duration="@integer/kg_security_flip_duration" />
 
 </set>
 
diff --git a/core/res/res/layout/keyguard_navigation.xml b/core/res/res/layout/keyguard_navigation.xml
index 569f93d..f9e3ef8 100644
--- a/core/res/res/layout/keyguard_navigation.xml
+++ b/core/res/res/layout/keyguard_navigation.xml
@@ -23,12 +23,15 @@
     android:gravity="left">
 
     <LinearLayout
+        android:id="@+id/keyguard_click_area"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal">
+        android:orientation="horizontal"
+        style="?android:attr/buttonBarButtonStyle"
+        android:padding="10dip"
+        android:clickable="true">
 
         <TextView
-            android:id="@+id/back"
             android:layout_width="20dip"
             android:layout_height="wrap_content"
             android:textSize="28dp"
@@ -36,7 +39,7 @@
 
         <!-- message area for security screen -->
         <TextView
-            android:id="@+id/message_area"
+            android:id="@+id/keyguard_message_area"
             android:layout_width="0dip"
             android:layout_height="wrap_content"
             android:layout_weight="1"
@@ -48,14 +51,4 @@
 
     </LinearLayout>
 
-    <!-- This is currently only uses for pattern unlock -->
-    <Button android:id="@+id/forgot_password_button"
-        android:layout_gravity="right"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
-        android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
-        android:drawablePadding="0dip"
-        android:visibility="gone"/>
-
 </LinearLayout>
diff --git a/core/res/res/layout/keyguard_password_view.xml b/core/res/res/layout/keyguard_password_view.xml
index b9a70e5..4ea471e 100644
--- a/core/res/res/layout/keyguard_password_view.xml
+++ b/core/res/res/layout/keyguard_password_view.xml
@@ -26,11 +26,6 @@
 
     <include layout="@layout/keyguard_navigation"/>
 
-    <Space
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1"/>
-
     <!-- Password entry field -->
     <!-- Note: the entire container is styled to look like the edit field,
          since the backspace/IME switcher looks better inside -->
@@ -84,6 +79,11 @@
 
     </LinearLayout>
 
+    <Space
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"/>
+
     <!-- Numeric keyboard -->
     <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/keyguard_pattern_view.xml b/core/res/res/layout/keyguard_pattern_view.xml
index 9cba609..954a92c 100644
--- a/core/res/res/layout/keyguard_pattern_view.xml
+++ b/core/res/res/layout/keyguard_pattern_view.xml
@@ -36,6 +36,15 @@
 
         <Space android:layout_gravity="fill" />
 
+        <Button android:id="@+id/forgot_password_button"
+            android:layout_gravity="right"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+            android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
+            android:drawablePadding="0dip"
+            android:visibility="gone"/>
+
         <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to
         the pattern view for it to compute its size. This is an unusual case, caused by
         LockPatternView's requirement to maintain a square aspect ratio based on the width
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
index 603fd7e..859eefc 100644
--- a/core/res/res/values/integers.xml
+++ b/core/res/res/values/integers.xml
@@ -17,5 +17,5 @@
 */
 -->
 <resources>
-    <integer name="flip_duration">300</integer>
+    <integer name="kg_security_flip_duration">150</integer>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6414df8..4dede0b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1309,7 +1309,8 @@
   <java-symbol type="id" name="two" />
   <java-symbol type="id" name="unlock_widget" />
   <java-symbol type="id" name="zero" />
-  <java-symbol type="id" name="message_area" />
+  <java-symbol type="id" name="keyguard_message_area" />
+  <java-symbol type="id" name="keyguard_click_area" />
   <java-symbol type="id" name="keyguard_selector_view" />
   <java-symbol type="id" name="keyguard_pattern_view" />
   <java-symbol type="id" name="keyguard_password_view" />
@@ -1338,6 +1339,7 @@
   <java-symbol type="integer" name="config_lidNavigationAccessibility" />
   <java-symbol type="integer" name="config_lidOpenRotation" />
   <java-symbol type="integer" name="config_longPressOnHomeBehavior" />
+  <java-symbol type="integer" name="kg_security_flip_duration" />
   <java-symbol type="layout" name="global_actions_item" />
   <java-symbol type="layout" name="global_actions_silent_mode" />
   <java-symbol type="layout" name="keyguard_screen_glogin_unlock" />
@@ -1440,7 +1442,12 @@
   <java-symbol type="string" name="kg_login_invalid_input" />
   <java-symbol type="string" name="kg_login_account_recovery_hint" />
   <java-symbol type="string" name="kg_login_checking_password" />
-
+  <java-symbol type="string" name="kg_too_many_failed_pin_attempts_dialog_message" />
+  <java-symbol type="string" name="kg_too_many_failed_pattern_attempts_dialog_message" />
+  <java-symbol type="string" name="kg_too_many_failed_password_attempts_dialog_message" />
+  <java-symbol type="string" name="kg_failed_attempts_almost_at_wipe" />
+  <java-symbol type="string" name="kg_failed_attempts_now_wiping" />
+  <java-symbol type="string" name="kg_failed_attempts_almost_at_login" />
 
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3178af0..894e272 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3665,33 +3665,111 @@
     <string name="display_manager_overlay_display_title">Overlay #<xliff:g id="id">%1$d</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string>
 
     <!-- Keyguard strings -->
+    <!-- Label shown on emergency call button in keyguard -->
     <string name="kg_emergency_call_label">Emergency call</string>
+    <!-- Message shown in pattern unlock after some number of unsuccessful attempts -->
     <string name="kg_forgot_pattern_button_text">Forgot Pattern</string>
+    <!-- Message shown when user enters wrong pattern -->
     <string name="kg_wrong_pattern">Wrong Pattern</string>
+    <!-- Message shown when user enters wrong password -->
     <string name="kg_wrong_password">Wrong Password</string>
+    <!-- Message shown when user enters wrong PIN -->
     <string name="kg_wrong_pin">Wrong PIN</string>
-    <string name="kg_too_many_failed_attempts_countdown">Too many attempts</string>
+    <!-- Countdown message shown after too many failed unlock attempts -->
+    <string name="kg_too_many_failed_attempts_countdown">Try again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+    <!-- Instructions for using the pattern unlock screen -->
     <string name="kg_pattern_instructions">Draw your pattern</string>
+    <!-- Instructions for using the SIM PIN unlock screen -->
     <string name="kg_sim_pin_instructions">Enter SIM PIN</string>
+    <!-- Instructions for using the PIN unlock screen -->
     <string name="kg_pin_instructions">Enter PIN</string>
+    <!-- Instructions for using the password unlock screen -->
     <string name="kg_password_instructions">Enter Password</string>
+    <!-- Hint shown in the PUK unlock screen PUK TextView -->
     <string name="kg_puk_enter_puk_hint">PUK code</string>
+    <!-- Hint shown in the PUK unlock screen PIN TextView -->
     <string name="kg_puk_enter_pin_hint">New PIN code</string>
+    <!-- Message shown in dialog while the device is unlocking the SIM card -->
     <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+    <!-- Message shown when the user enters the wrong PIN code -->
     <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string>
+    <!-- Message shown when the user enters an invalid SIM pin password in PUK screen -->
     <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string>
+    <!-- Message shown when the user enters an invalid PUK code in the PUK screen -->
     <string name="kg_invalid_sim_puk_hint">Type a PUK that is 8 numbers or longer.</string>
+    <!-- Instructions for PUK unlock screen -->
     <string name="kg_sim_puk_recovery_hint">Type PUK and new PIN code</string>
+    <!-- Message shown when the user enters an invalid PUK code -->
     <string name="kg_invalid_puk">The PUK you typed isn\'t correct.</string>
-
+    <!-- Message shown when the user exceeds the maximum number of pattern attempts -->
     <string name="kg_login_too_many_attempts">Too many pattern attempts</string>
+    <!-- Instructions show in account unlock screen allowing user to enter their email password -->
     <string name="kg_login_instructions">To unlock, sign in with your Google account.</string>
+    <!-- Hint shown in TextView in account unlock screen of keyguard -->
     <string name="kg_login_username_hint">Username (email)</string>
+    <!-- Hint shown in TextView in account unlock screen of keyguard -->
     <string name="kg_login_password_hint">Password</string>
+    <!-- Label shown on sign in button on account unlock screen of keyguard -->
     <string name="kg_login_submit_button">Sign in</string>
+    <!-- Message shown when the user enters an invalid username/password combination in account unlock screen of keyguard -->
     <string name="kg_login_invalid_input">Invalid username or password.</string>
+    <!-- Hint text shown when user has too many failed password attempts in account unlock screen of keyguard -->
     <string name="kg_login_account_recovery_hint">Forgot your username or password\?\nVisit <b>google.com/accounts/recovery</b>.</string>
+    <!-- Message shown while device checks username/password in account unlock screen of keyguard -->
     <string name="kg_login_checking_password">Checking\u2026</string>
+    <!-- Message shown in dialog when max number of attempts are reached for PIN screen of keyguard -->
+    <string name="kg_too_many_failed_pin_attempts_dialog_message">
+        You have incorrectly typed your PIN <xliff:g id="number">%d</xliff:g> times.
+        \n\nTry again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <!-- Message shown in dialog when max number of attempts are reached for password screen of keyguard -->
+    <string name="kg_too_many_failed_password_attempts_dialog_message">
+        You have incorrectly typed your password <xliff:g id="number">%d</xliff:g> times.
+        \n\nTry again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <string name="kg_too_many_failed_pattern_attempts_dialog_message">
+        You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+        \n\nTry again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <!-- Message shown when user is almost at the limit of password attempts where the device will be wiped. -->
+    <string name="kg_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>
+    <!-- Message shown when user is almost at the limit of password attempts where the device will be wiped. -->
+    <string name="kg_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>
+    <!-- Message shown in dialog when user has exceeded the maximum attempts and the device will now be wiped -->
+    <string name="kg_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>
+    <!-- Message shown in dialog when user has exceeded the maximum attempts and the device will now be wiped -->
+    <string name="kg_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>
+    <!-- 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="kg_failed_attempts_almost_at_login" product="tablet">
+       You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       you will be asked to unlock your tablet using an email account.\n\n
+       Try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+    <!-- 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="kg_failed_attempts_almost_at_login" product="default">
+       You have incorrectly drawn your unlock pattern <xliff:g id="number">%d</xliff:g> times.
+       After <xliff:g id="number">%d</xliff:g> more unsuccessful attempts,
+       you will be asked to unlock your phone using an email account.\n\n
+       Try again in <xliff:g id="number">%d</xliff:g> seconds.
+    </string>
+
     <string name="kg_temp_back_string"> &lt; </string> <!-- TODO: remove this -->
 
 </resources>
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index d74a5e7..2551c04e 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -16,8 +16,11 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
@@ -31,9 +34,10 @@
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
 import android.widget.Button;
 import android.widget.ViewFlipper;
@@ -74,8 +78,8 @@
     private ViewFlipper mViewFlipper;
     private Button mEmergencyDialerButton;
     private boolean mEnableMenuKey;
-    private boolean mScreenOn;
     private boolean mIsVerifyUnlockOnly;
+    private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView
     private int mCurrentSecurityId = SECURITY_SELECTOR_ID;
 
     // KeyguardSecurityViews
@@ -116,7 +120,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        mCallback.keyguardDoneDrawing();
+        mViewMediatorCallback.keyguardDoneDrawing();
     }
 
     @Override
@@ -196,29 +200,25 @@
         }
 
         public boolean isVerifyUnlockOnly() {
-            // TODO
-            return false;
+            return mIsVerifyUnlockOnly;
         }
 
         public void reportSuccessfulUnlockAttempt() {
-            KeyguardUpdateMonitor.getInstance(mContext).clearFailedAttempts();
+            KeyguardUpdateMonitor.getInstance(mContext).clearFailedUnlockAttempts();
         }
 
         public void reportFailedUnlockAttempt() {
             // TODO: handle biometric attempt differently.
-            KeyguardUpdateMonitor.getInstance(mContext).reportFailedAttempt();
+            KeyguardHostView.this.reportFailedUnlockAttempt();
         }
 
         public int getFailedAttempts() {
-            return KeyguardUpdateMonitor.getInstance(mContext).getFailedAttempts();
+            return KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts();
         }
 
-        public void showBackupUnlock() {
-            // TODO
-        }
-
-        public void keyguardDoneDrawing() {
-            mViewMediatorCallback.keyguardDoneDrawing();
+        @Override
+        public void showBackupSecurity() {
+            KeyguardHostView.this.showBackupSecurity();
         }
 
         @Override
@@ -228,6 +228,9 @@
 
     };
 
+    /**
+     * Shows the emergency dialer or returns the user to the existing call.
+     */
     public void takeEmergencyCallAction() {
         mCallback.userActivity(EMERGENCY_CALL_TIMEOUT);
         if (TelephonyManager.getDefault().getCallState()
@@ -241,19 +244,147 @@
         }
     }
 
-    protected void showNextSecurityScreenOrFinish(boolean authenticated) {
+    private void showDialog(String title, String message) {
+        final AlertDialog dialog = new AlertDialog.Builder(mContext)
+            .setTitle(title)
+            .setMessage(message)
+            .setNeutralButton(com.android.internal.R.string.ok, null)
+            .create();
+        if (!(mContext instanceof Activity)) {
+            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        }
+        dialog.show();
+    }
+
+    private void showTimeoutDialog() {
+        int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
+        int messageId = 0;
+
+        switch (mSecurityModel.getSecurityMode()) {
+            case Pattern:
+                messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
+                break;
+
+            case Password: {
+                    final boolean isPin = mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
+                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+                    messageId = isPin ? R.string.kg_too_many_failed_pin_attempts_dialog_message
+                            : R.string.kg_too_many_failed_password_attempts_dialog_message;
+                }
+                break;
+        }
+
+        if (messageId != 0) {
+            final String message = mContext.getString(messageId,
+                    KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(),
+                    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.kg_failed_attempts_almost_at_wipe,
+                attempts, remaining);
+        showDialog(null, message);
+    }
+
+    private void showWipeDialog(int attempts) {
+        String message = mContext.getString(R.string.kg_failed_attempts_now_wiping, attempts);
+        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.kg_failed_attempts_almost_at_login,
+                count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds);
+        showDialog(null, message);
+    }
+
+    private void reportFailedUnlockAttempt() {
+        final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time
+
+        if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
+
+        SecurityMode mode = mSecurityModel.getSecurityMode();
+        final boolean usingPattern = mode == KeyguardSecurityModel.SecurityMode.Pattern;
+
+        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 {
+            boolean showTimeout =
+                (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0;
+            if (usingPattern && mEnableFallback) {
+                if (failedAttempts == failedAttemptWarning) {
+                    showAlmostAtAccountLoginDialog();
+                    showTimeout = false; // don't show both dialogs
+                } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
+                    mLockPatternUtils.setPermanentlyLocked(true);
+                    showSecurityScreen(SECURITY_ACCOUNT_ID);
+                    // don't show timeout dialog because we show account unlock screen next
+                    showTimeout = false;
+                }
+            }
+            if (showTimeout) {
+                showTimeoutDialog();
+            }
+        }
+        monitor.reportFailedUnlockAttempt();
+        mLockPatternUtils.reportFailedPasswordAttempt();
+    }
+
+    /**
+     * Shows the backup security screen for the current security mode.  This could be used for
+     * password recovery screens but is currently only used for pattern unlock to show the
+     * account unlock screen and biometric unlock to show the user's normal unlock.
+     */
+    private void showBackupSecurity() {
+        SecurityMode currentMode = mSecurityModel.getSecurityMode();
+        SecurityMode backup = mSecurityModel.getBackupFor(currentMode);
+        showSecurityScreen(getSecurityViewIdForMode(backup));
+    }
+
+    private void showNextSecurityScreenOrFinish(boolean authenticated) {
         boolean finish = false;
         if (SECURITY_SELECTOR_ID == mCurrentSecurityId) {
-            int realSecurityId = getSecurityViewIdForMode(mSecurityModel.getSecurityMode());
-            if (realSecurityId == mCurrentSecurityId) {
+            SecurityMode securityMode = mSecurityModel.getSecurityMode();
+            // Allow an alternate, such as biometric unlock
+            // TODO: un-comment when face unlock is working again:
+            // securityMode = mSecurityModel.getAlternateFor(securityMode);
+            int realSecurityId = getSecurityViewIdForMode(securityMode);
+            if (SECURITY_SELECTOR_ID == realSecurityId) {
                 finish = true; // no security required
             } else {
                 showSecurityScreen(realSecurityId); // switch to the "real" security view
             }
         } else if (authenticated) {
-            if ((mCurrentSecurityId == SECURITY_PATTERN_ID
+            if (mCurrentSecurityId == SECURITY_PATTERN_ID
                 || mCurrentSecurityId == SECURITY_PASSWORD_ID
-                || mCurrentSecurityId == SECURITY_ACCOUNT_ID)) {
+                || mCurrentSecurityId == SECURITY_ACCOUNT_ID
+                || mCurrentSecurityId == SECURITY_BIOMETRIC_ID) {
                 finish = true;
             }
         } else {
@@ -309,6 +440,7 @@
 
     @Override
     public void reset() {
+        mIsVerifyUnlockOnly = false;
         requestFocus();
     }
 
@@ -330,6 +462,12 @@
         return null;
     }
 
+    /**
+     * Switches to the given security view unless it's already being shown, in which case
+     * this is a no-op.
+     *
+     * @param securityViewId
+     */
     private void showSecurityScreen(int securityViewId) {
 
         if (securityViewId == mCurrentSecurityId) return;
@@ -352,19 +490,22 @@
                 break;
             }
         }
+
+        // Discard current runnable if we're switching back to the selector view
+        if (securityViewId == SECURITY_SELECTOR_ID) {
+            setOnDismissRunnable(null);
+        }
     }
 
     @Override
     public void onScreenTurnedOn() {
         if (DEBUG) Log.d(TAG, "screen on");
-        mScreenOn = true;
         showSecurityScreen(mCurrentSecurityId);
     }
 
     @Override
     public void onScreenTurnedOff() {
         if (DEBUG) Log.d(TAG, "screen off");
-        mScreenOn = false;
         showSecurityScreen(SECURITY_SELECTOR_ID);
     }
 
@@ -469,7 +610,8 @@
     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
     private boolean shouldEnableMenuKey() {
         final Resources res = getResources();
-        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
+        final boolean configDisabled = res.getBoolean(
+                com.android.internal.R.bool.config_disableMenuKeyInLockScreen);
         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
         return !configDisabled || isTestHarness || fileOverride;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
index d3feced..4f29825 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
@@ -25,12 +25,15 @@
 
     private TextView mMessageArea;
     private KeyguardSecurityView mKeyguardSecurityView;
+    private View mClickArea;
 
     public KeyguardNavigationManager(KeyguardSecurityView view) {
         mKeyguardSecurityView = view;
-        mMessageArea = (TextView) ((View) view).findViewById(R.id.message_area);
+        mMessageArea = (TextView) ((View) view).findViewById(R.id.keyguard_message_area);
         mMessageArea.setSelected(true); // Make marquee work
-        mMessageArea.setOnClickListener(new View.OnClickListener() {
+
+        mClickArea = ((View) view).findViewById(R.id.keyguard_click_area);
+        mClickArea.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 mKeyguardSecurityView.getCallback().dismiss(false);
             }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
index a95cfcb..3a32b5b 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
@@ -129,7 +129,7 @@
         mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
         mForgotPatternButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
-                mCallback.showBackupUnlock();
+                mCallback.showBackupSecurity();
             }
         });
 
@@ -233,6 +233,7 @@
                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
                 mCallback.dismiss(true); // keyguardDone(true)
                 KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
+                mTotalFailedPatternAttempts = 0;
             } else {
                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
                     mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
@@ -313,13 +314,15 @@
         mLockPatternView.clearPattern();
         mLockPatternView.setEnabled(false);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
+        updateFooter(FooterMode.ForgotLockPattern);
+
         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
 
             @Override
             public void onTick(long millisUntilFinished) {
                 final int secondsRemaining = (int) (millisUntilFinished / 1000);
                 mNavigationManager.setMessage(
-                        R.string.kg_too_many_failed_attempts_countdown,secondsRemaining);
+                        R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
             }
 
             @Override
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
index 36342a5..3b8df5d 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
@@ -53,17 +53,12 @@
     int getFailedAttempts();
 
     /**
-     * Shows the backup unlock for the current method.  If none available, this call is a NOP.
+     * Shows the backup security for the current method.  If none available, this call is a no-op.
      */
-    void showBackupUnlock();
+    void showBackupSecurity();
 
     /**
-     * Used to inform keyguard when the current view is done drawing.
-     */
-    void keyguardDoneDrawing();
-
-    /**
-     * Sets a runnable to launch after the user enters their
+     * Sets a runnable to launch after the user successfully enters their credentials.
      * @param runnable
      */
     void setOnDismissRunnable(Runnable runnable);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
index d041dd3..a19b35d 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
@@ -48,38 +48,85 @@
         mLockPatternUtils = utils;
     }
 
+    /**
+     * This returns false if there is any condition that indicates that the biometric unlock should
+     * not be used before the next time the unlock screen is recreated.  In other words, if this
+     * returns false there is no need to even construct the biometric unlock.
+     */
+    private boolean isBiometricUnlockEnabled() {
+        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        final boolean backupIsTimedOut =
+                monitor.getFailedUnlockAttempts() >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
+        return mLockPatternUtils.usingBiometricWeak()
+                && mLockPatternUtils.isBiometricWeakInstalled()
+                && !monitor.getMaxBiometricUnlockAttemptsReached()
+                && !backupIsTimedOut;
+    }
+
     SecurityMode getSecurityMode() {
         KeyguardUpdateMonitor mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         final IccCardConstants.State simState = mUpdateMonitor.getSimState();
+        SecurityMode mode = SecurityMode.None;
         if (simState == IccCardConstants.State.PIN_REQUIRED) {
-            return SecurityMode.SimPin;
+            mode = SecurityMode.SimPin;
         } else if (simState == IccCardConstants.State.PUK_REQUIRED) {
-            return SecurityMode.SimPuk;
+            mode = SecurityMode.SimPuk;
         } else {
-            final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality();
-            switch (mode) {
+            final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+            switch (security) {
                 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
-                    return mLockPatternUtils.isLockPasswordEnabled() ?
+                    mode = mLockPatternUtils.isLockPasswordEnabled() ?
                             SecurityMode.Password : SecurityMode.None;
+                    break;
 
                 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
                 case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
                     if (mLockPatternUtils.isLockPatternEnabled()) {
-                        return mLockPatternUtils.isPermanentlyLocked() ?
+                        mode = mLockPatternUtils.isPermanentlyLocked() ?
                             SecurityMode.Account : SecurityMode.Pattern;
-                    } else {
-                        return SecurityMode.None;
                     }
+                    break;
+
                 default:
-                   throw new IllegalStateException("Unknown unlock mode:" + mode);
+                    throw new IllegalStateException("Unknown unlock mode:" + mode);
             }
         }
+        return mode;
     }
 
+    /**
+     * Some unlock methods can have an alternate, such as biometric unlocks (e.g. face unlock).
+     * This function decides if an alternate unlock is available and returns it. Otherwise,
+     * returns @param mode.
+     *
+     * @param mode the mode we want the alternate for
+     * @return alternate or the given mode
+     */
+    SecurityMode getAlternateFor(SecurityMode mode) {
+        if (isBiometricUnlockEnabled()
+                && (mode == SecurityMode.Password || mode == SecurityMode.Pattern)) {
+            return SecurityMode.Biometric;
+        }
+        return mode; // no alternate, return what was given
+    }
+
+    /**
+     * Some unlock methods can have a backup which gives the user another way to get into
+     * the device. This is currently only supported for Biometric and Pattern unlock.
+     *
+     * @param mode the mode we want the backup for
+     * @return backup method or given mode
+     */
     SecurityMode getBackupFor(SecurityMode mode) {
-        return SecurityMode.None;  // TODO: handle biometric unlock, etc.
+        switch(mode) {
+            case Biometric:
+                return getSecurityMode();
+            case Pattern:
+                return SecurityMode.Account;
+        }
+        return mode; // no backup, return what was given
     }
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
index dad0dff..39df5a2 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
@@ -53,8 +53,8 @@
  *
  * Note: under time crunch, this has been extended to include some stuff that
  * doesn't really belong here.  see {@link #handleBatteryUpdate} where it shutdowns
- * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()}
- * and {@link #clearFailedAttempts()}.  Maybe we should rename this 'KeyguardContext'...
+ * the device, and {@link #getFailedUnlockAttempts()}, {@link #reportFailedAttempt()}
+ * and {@link #clearFailedUnlockAttempts()}.  Maybe we should rename this 'KeyguardContext'...
  */
 public class KeyguardUpdateMonitor {
 
@@ -658,16 +658,16 @@
         return mDeviceProvisioned;
     }
 
-    public int getFailedAttempts() {
+    public int getFailedUnlockAttempts() {
         return mFailedAttempts;
     }
 
-    public void clearFailedAttempts() {
+    public void clearFailedUnlockAttempts() {
         mFailedAttempts = 0;
         mFailedBiometricUnlockAttempts = 0;
     }
 
-    public void reportFailedAttempt() {
+    public void reportFailedUnlockAttempt() {
         mFailedAttempts++;
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
index d6733ea..3ee82f7 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
@@ -986,7 +986,7 @@
             mHandler.sendMessage(msg);
 
             if (authenticated) {
-                mUpdateMonitor.clearFailedAttempts();
+                mUpdateMonitor.clearFailedUnlockAttempts();
             }
 
             if (mExitSecureCallback != null) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
index d778129..e8078fd 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
@@ -53,12 +53,9 @@
         super(context, attrs, defStyle);
         if (sLeftOverscrollDrawable == null) {
             Resources res = context.getResources();
-            sLeftOverscrollDrawable = res.getDrawable(
-                    com.android.internal.R.drawable.kg_widget_overscroll_layer_left);
-            sRightOverscrollDrawable = res.getDrawable(
-                    com.android.internal.R.drawable.kg_widget_overscroll_layer_right);
-            sWidgetPagePadding =
-                    res.getDimensionPixelSize(com.android.internal.R.dimen.kg_widget_page_padding);
+            sLeftOverscrollDrawable = res.getDrawable(R.drawable.kg_widget_overscroll_layer_left);
+            sRightOverscrollDrawable = res.getDrawable(R.drawable.kg_widget_overscroll_layer_right);
+            sWidgetPagePadding = res.getDimensionPixelSize(R.dimen.kg_widget_page_padding);
         }
         setPadding(sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding);
     }