Fix 2332563: Add password-lock support to lockscreen
diff --git a/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 66c5159..ccb7902 100644
--- a/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -111,7 +111,12 @@
         /**
          * Unlock by entering an account's login and password.
          */
-        Account
+        Account,
+
+        /**
+         * Unlock by entering a password or PIN
+         */
+        Password
     }
 
     /**
@@ -268,7 +273,7 @@
             public void reportFailedPatternAttempt() {
                 mUpdateMonitor.reportFailedAttempt();
                 final int failedAttempts = mUpdateMonitor.getFailedAttempts();
-                if (DEBUG) Log.d(TAG, 
+                if (DEBUG) Log.d(TAG,
                     "reportFailedPatternAttempt: #" + failedAttempts +
                     " (enableFallback=" + mEnableFallback + ")");
                 if (mEnableFallback && failedAttempts ==
@@ -309,7 +314,7 @@
         mLockScreen = createLockScreen();
         addView(mLockScreen);
         final UnlockMode unlockMode = getUnlockMode();
-        if (DEBUG) Log.d(TAG, 
+        if (DEBUG) Log.d(TAG,
             "LockPatternKeyguardView ctor: about to createUnlockScreenFor; mEnableFallback="
             + mEnableFallback);
         mUnlockScreen = createUnlockScreenFor(unlockMode);
@@ -434,16 +439,25 @@
 
     private boolean isSecure() {
         UnlockMode unlockMode = getUnlockMode();
-        if (unlockMode == UnlockMode.Pattern) {
-            return mLockPatternUtils.isLockPatternEnabled();
-        } else if (unlockMode == UnlockMode.SimPin) {
-            return mUpdateMonitor.getSimState() == IccCard.State.PIN_REQUIRED
-                        || mUpdateMonitor.getSimState() == IccCard.State.PUK_REQUIRED;
-        } else if (unlockMode == UnlockMode.Account) {
-            return true;
-        } else {
-            throw new IllegalStateException("unknown unlock mode " + unlockMode);
+        boolean secure = false;
+        switch (unlockMode) {
+            case Pattern:
+                secure = mLockPatternUtils.isLockPatternEnabled();
+                break;
+            case SimPin:
+                secure = mUpdateMonitor.getSimState() == IccCard.State.PIN_REQUIRED
+                            || mUpdateMonitor.getSimState() == IccCard.State.PUK_REQUIRED;
+                break;
+            case Account:
+                secure = true;
+                break;
+            case Password:
+                secure = mLockPatternUtils.isLockPasswordEnabled();
+                break;
+            default:
+                throw new IllegalStateException("unknown unlock mode " + unlockMode);
         }
+        return secure;
     }
 
     private void updateScreen(final Mode mode) {
@@ -524,6 +538,12 @@
                 // "permanently locked" state.)
                 return createUnlockScreenFor(UnlockMode.Pattern);
             }
+        } else if (unlockMode == UnlockMode.Password) {
+            return new PasswordUnlockScreen(
+                    mContext,
+                    mLockPatternUtils,
+                    mUpdateMonitor,
+                    mKeyguardScreenCallback);
         } else {
             throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
         }
@@ -575,13 +595,29 @@
      */
     private UnlockMode getUnlockMode() {
         final IccCard.State simState = mUpdateMonitor.getSimState();
+        UnlockMode currentMode;
         if (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED) {
-            return UnlockMode.SimPin;
+            currentMode = UnlockMode.SimPin;
         } else {
-            return (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) ?
-                    UnlockMode.Account:
-                    UnlockMode.Pattern;
+            final int mode = mLockPatternUtils.getPasswordMode();
+            switch (mode) {
+                case LockPatternUtils.MODE_PIN:
+                case LockPatternUtils.MODE_PASSWORD:
+                    currentMode = UnlockMode.Password;
+                    break;
+                case LockPatternUtils.MODE_PATTERN:
+                    // "forgot pattern" button is only available in the pattern mode...
+                    if (mForgotPattern && mLockPatternUtils.isPermanentlyLocked()) {
+                        currentMode = UnlockMode.Account;
+                    } else {
+                        currentMode = UnlockMode.Pattern;
+                    }
+                    break;
+                default:
+                   throw new IllegalStateException("Unknown unlock mode:" + mode);
+            }
         }
+        return currentMode;
     }
 
     private void showTimeoutDialog() {
diff --git a/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
index 8a62cbd..ed5a058 100644
--- a/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
+++ b/policy/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
@@ -50,12 +50,7 @@
     }
 
     public boolean isSecure() {
-        return isLockPatternSecure() || isSimPinSecure();
-    }
-
-    private boolean isLockPatternSecure() {
-        return mLockPatternUtils.isLockPatternEnabled() && mLockPatternUtils
-                .savedPatternExists();
+        return mLockPatternUtils.isSecure() || isSimPinSecure();
     }
 
     private boolean isSimPinSecure() {
diff --git a/policy/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/com/android/internal/policy/impl/PasswordUnlockScreen.java
new file mode 100644
index 0000000..bb1950a
--- /dev/null
+++ b/policy/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.content.Context;
+
+import com.android.internal.telephony.IccCard.State;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.text.Editable;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.internal.R;
+
+/**
+ * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter
+ * an unlock password
+ */
+public class PasswordUnlockScreen extends LinearLayout implements KeyguardScreen, View.OnClickListener,
+        KeyguardUpdateMonitor.ConfigurationChangeCallback, KeyguardUpdateMonitor.InfoCallback {
+
+    private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
+
+    private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final KeyguardScreenCallback mCallback;
+
+    private final boolean mCreatedWithKeyboardOpen;
+
+    private TextView mPasswordTextView;
+    private TextView mOkButton;
+    private TextView mEmergencyCallButton;
+    private View mBackSpaceButton;
+    private TextView mCarrier;
+    private LockPatternUtils mLockPatternUtils;
+    private Button mCancelButton;
+    private int mPasswordAttempts = 0;
+    private int mMinimumPasswordLength = 4; // TODO: get from policy store
+
+    private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
+
+    public PasswordUnlockScreen(Context context, LockPatternUtils lockPatternUtils,
+            KeyguardUpdateMonitor updateMonitor, KeyguardScreenCallback callback) {
+        super(context);
+        mUpdateMonitor = updateMonitor;
+        mCallback = callback;
+        mCreatedWithKeyboardOpen = mUpdateMonitor.isKeyboardOpen();
+
+        LayoutInflater layoutInflater = LayoutInflater.from(context);
+        if (mCreatedWithKeyboardOpen) {
+            layoutInflater.inflate(R.layout.keyguard_screen_password_landscape, this, true);
+        } else {
+            layoutInflater.inflate(R.layout.keyguard_screen_password_portrait, this, true);
+            new TouchInput();
+        }
+
+        mPasswordTextView = (TextView) findViewById(R.id.pinDisplay);
+        mBackSpaceButton = findViewById(R.id.backspace);
+        mBackSpaceButton.setOnClickListener(this);
+
+        // The cancel button is not used on this screen.
+        mCancelButton = (Button) findViewById(R.id.cancel);
+        if (mCancelButton != null) {
+            mCancelButton.setText("");
+        }
+
+        mEmergencyCallButton = (TextView) findViewById(R.id.emergencyCall);
+        mOkButton = (TextView) findViewById(R.id.ok);
+
+        mPasswordTextView.setFocusable(false);
+
+        mEmergencyCallButton.setOnClickListener(this);
+        mOkButton.setOnClickListener(this);
+
+        mUpdateMonitor.registerConfigurationChangeCallback(this);
+
+        mLockPatternUtils = lockPatternUtils;
+        mCarrier = (TextView) findViewById(R.id.carrier);
+        // until we get an update...
+        mCarrier.setText(LockScreen.getCarrierString(mUpdateMonitor.getTelephonyPlmn(),
+                        mUpdateMonitor.getTelephonySpn()));
+
+        updateMonitor.registerInfoCallback(this);
+        updateMonitor.registerConfigurationChangeCallback(this);
+
+        setFocusableInTouchMode(true);
+    }
+
+    /** {@inheritDoc} */
+    public boolean needsInput() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public void onPause() {
+
+    }
+
+    /** {@inheritDoc} */
+    public void onResume() {
+        // start fresh
+        mPasswordTextView.setText("");
+    }
+
+    /** {@inheritDoc} */
+    public void cleanUp() {
+        mUpdateMonitor.removeCallback(this);
+    }
+
+    public void onClick(View v) {
+        if (v == mBackSpaceButton) {
+            final Editable digits = mPasswordTextView.getEditableText();
+            final int len = digits.length();
+            if (len > 0) {
+                digits.delete(len-1, len);
+            }
+        } else if (v == mEmergencyCallButton) {
+            mCallback.takeEmergencyCallAction();
+        } else if (v == mOkButton) {
+            verifyPasswordAndUnlock();
+        }
+        mCallback.pokeWakelock();
+    }
+
+    private void verifyPasswordAndUnlock() {
+        String entry = mPasswordTextView.getText().toString();
+        if (mLockPatternUtils.checkPassword(entry)) {
+            mPasswordAttempts = 0;
+            mCallback.keyguardDone(true);
+        } else if (entry.length() >= mMinimumPasswordLength ) {
+            // to avoid accidental lockout, only count attempts that are long enough to be a
+            // real password. This may require some tweaking.
+            mPasswordAttempts++;
+        }
+        mPasswordTextView.setText("");
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return true;
+        }
+
+        final char match = event.getMatch(DIGITS);
+        if (match != 0) {
+            reportDigit(match - '0');
+            return true;
+        }
+        if (keyCode == KeyEvent.KEYCODE_DEL) {
+            mPasswordTextView.onKeyDown(keyCode, event);
+            return true;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_ENTER) {
+            verifyPasswordAndUnlock();
+            return true;
+        }
+
+        return false;
+    }
+
+    private void reportDigit(int digit) {
+        mPasswordTextView.append(Integer.toString(digit));
+    }
+
+    public void onOrientationChange(boolean inPortrait) {
+
+    }
+
+    public void onKeyboardChange(boolean isKeyboardOpen) {
+        if (isKeyboardOpen != mCreatedWithKeyboardOpen) {
+            mCallback.recreateMe();
+        }
+    }
+
+    /**
+     * Helper class to handle input from touch dialer.  Only relevant when
+     * the keyboard is shut.
+     */
+    private class TouchInput implements View.OnClickListener {
+        private int mDigitIds[] = { R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four,
+                R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine };
+        private TextView mCancelButton;
+        private TouchInput() {
+            for (int i = 0; i < mDigitIds.length; i++) {
+                Button button = (Button) findViewById(mDigitIds[i]);
+                button.setOnClickListener(this);
+                button.setText(Integer.toString(i));
+            }
+            mCancelButton = (TextView) findViewById(R.id.cancel);
+            mCancelButton.setOnClickListener(this);
+            mOkButton = (TextView) findViewById(R.id.ok);
+            mOkButton.setOnClickListener(this);
+        }
+
+        public void onClick(View v) {
+            if (v == mCancelButton) {
+                return;
+            }
+            if (v == mOkButton) {
+                verifyPasswordAndUnlock();
+            }
+
+            final int digit = checkDigit(v);
+            if (digit >= 0) {
+                mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
+                reportDigit(digit);
+            }
+        }
+
+        private int checkDigit(View v) {
+            int digit = -1;
+            for (int i = 0; i < mDigitIds.length; i++) {
+                if (v.getId() == mDigitIds[i]) {
+                    digit = i;
+                    break;
+                }
+            }
+            return digit;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+        mCarrier.setText(LockScreen.getCarrierString(plmn, spn));
+    }
+
+    public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+
+    }
+
+    public void onRingerModeChanged(int state) {
+
+    }
+
+    public void onTimeChanged() {
+
+    }
+
+    public void onSimStateChanged(State simState) {
+
+    }
+}
diff --git a/policy/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/com/android/internal/policy/impl/SimUnlockScreen.java
index 3881d11..8c738a7 100644
--- a/policy/com/android/internal/policy/impl/SimUnlockScreen.java
+++ b/policy/com/android/internal/policy/impl/SimUnlockScreen.java
@@ -27,7 +27,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
-import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.internal.R;
@@ -167,6 +166,7 @@
             }
             mCallback.pokeWakelock();
         } else if (v == mEmergencyCallButton) {
+            mCallback.pokeWakelock();
             mCallback.takeEmergencyCallAction();
         } else if (v == mOkButton) {
             checkPin();
@@ -219,8 +219,8 @@
                     mHeaderText.setText(R.string.keyguard_password_wrong_pin_code);
                     mPinText.setText("");
                     mEnteredDigits = 0;
-                    mCallback.pokeWakelock();
                 }
+                mCallback.pokeWakelock();
             }
         }.start();
     }
diff --git a/policy/com/android/internal/policy/impl/UnlockScreen.java b/policy/com/android/internal/policy/impl/UnlockScreen.java
index e090ac5..30ab879 100644
--- a/policy/com/android/internal/policy/impl/UnlockScreen.java
+++ b/policy/com/android/internal/policy/impl/UnlockScreen.java
@@ -197,6 +197,7 @@
         // emergency call buttons
         final OnClickListener emergencyClick = new OnClickListener() {
             public void onClick(View v) {
+                mCallback.pokeWakelock();
                 mCallback.takeEmergencyCallAction();
             }
         };