Visualize password requirements and their fulfillment

1. Aggregate policies and generate the requirements
2. When user modifies the password, check is each requirement fulfilled
   Update the view accordingly.

Change-Id: I962ed3b81ce844006be1024a493e94ce52a3fdec
Fix: 24900754
diff --git a/res/drawable/ic_check_green_24dp.xml b/res/drawable/ic_check_green_24dp.xml
new file mode 100644
index 0000000..c836a02
--- /dev/null
+++ b/res/drawable/ic_check_green_24dp.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2016 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"
+        android:fillColor="#0F9D58"/>
+</vector>
diff --git a/res/drawable/ic_cross_grey_24dp.xml b/res/drawable/ic_cross_grey_24dp.xml
new file mode 100644
index 0000000..312c034
--- /dev/null
+++ b/res/drawable/ic_cross_grey_24dp.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2016 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
+        android:fillColor="#757575"/>
+</vector>
diff --git a/res/layout/choose_lock_password.xml b/res/layout/choose_lock_password.xml
index 99657b4..84485d0 100644
--- a/res/layout/choose_lock_password.xml
+++ b/res/layout/choose_lock_password.xml
@@ -14,7 +14,6 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-
 <com.android.setupwizardlib.GlifLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:settings="http://schemas.android.com/apk/res-auto"
@@ -31,9 +30,11 @@
         android:orientation="vertical">
 
         <!-- header text ('Enter Pin') -->
-        <TextView android:id="@+id/headerText"
+        <TextView
+            android:id="@+id/headerText"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:accessibilityLiveRegion="polite"
             android:gravity="center"
             android:lines="2"
             android:textAppearance="?android:attr/textAppearanceMedium"/>
@@ -50,32 +51,43 @@
             style="@style/TextAppearance.PasswordEntry"/>
 
         <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:gravity="end"
-            android:orientation="horizontal">
+                android:id="@+id/bottom_container"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
 
-            <!-- left : cancel -->
-            <Button android:id="@+id/cancel_button"
-                style="@style/SetupWizardButton.Negative"
-                android:layout_width="wrap_content"
+            <android.support.v7.widget.RecyclerView
+                    android:id="@+id/password_requirements_view"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"/>
+
+            <LinearLayout
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:text="@string/lockpassword_cancel_label" />
+                android:clipChildren="false"
+                android:clipToPadding="false"
+                android:gravity="end"
+                android:orientation="horizontal">
 
-            <Space
-                android:layout_width="0dp"
-                android:layout_height="0dp"
-                android:layout_weight="1" />
+                <!-- left : cancel -->
+                <Button android:id="@+id/cancel_button"
+                    style="@style/SetupWizardButton.Negative"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lockpassword_cancel_label" />
 
-            <!-- right : continue -->
-            <Button android:id="@+id/next_button"
-                style="@style/SetupWizardButton.Positive"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/lockpassword_continue_label" />
+                <Space
+                    android:layout_width="0dp"
+                    android:layout_height="0dp"
+                    android:layout_weight="1" />
 
+                <!-- right : continue -->
+                <Button android:id="@+id/next_button"
+                    style="@style/SetupWizardButton.Positive"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/lockpassword_continue_label" />
+            </LinearLayout>
         </LinearLayout>
 
         <!-- Spacer between password entry and keyboard -->
diff --git a/res/layout/password_requirement_item.xml b/res/layout/password_requirement_item.xml
new file mode 100644
index 0000000..df7f45c
--- /dev/null
+++ b/res/layout/password_requirement_item.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/description_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingStart="4dp"
+        android:textSize="14sp"/>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b2d0570..d65bdfb 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -305,4 +305,9 @@
     <dimen name="support_tile_min_height">48dp</dimen>
     <!-- support spacer layout height -->
     <dimen name="support_spacer_height">8dp</dimen>
+
+    <dimen name="password_requirement_textsize">14sp</dimen>
+    <!-- Visible vertical space we want to show below password edittext field when ime is shown.
+         The unit is sp as it is related to the text size of password requirement item. -->
+    <dimen name="visible_vertical_space_below_password">20sp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5a7e37a..b8b4273 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1166,7 +1166,7 @@
     <string name="lock_profile_wipe_dismiss">Dismiss</string>
 
     <!-- Hint shown in dialog screen when password is too short -->
-    <string name="lockpassword_password_too_short">Password must be at least %d characters</string>
+    <string name="lockpassword_password_too_short">Must be at least %d characters</string>
     <!-- Hint shown in dialog screen when PIN is too short -->
     <string name="lockpassword_pin_too_short">PIN must be at least %d digits</string>
 
@@ -1174,62 +1174,62 @@
     <string name="lockpassword_continue_label">Continue</string>
 
     <!-- Error shown in popup when password is too long -->
-    <string name="lockpassword_password_too_long">Password must be fewer than <xliff:g id="number" example="17">%d</xliff:g> characters.</string>
+    <string name="lockpassword_password_too_long">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> characters.</string>
     <!-- Error shown in popup when PIN is too long -->
-    <string name="lockpassword_pin_too_long">PIN must be fewer than <xliff:g id="number" example="17">%d</xliff:g> digits.</string>
+    <string name="lockpassword_pin_too_long">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> digits.</string>
 
     <!-- Error shown when in PIN mode and user enters a non-digit -->
-    <string name="lockpassword_pin_contains_non_digits">PIN must contain only digits 0-9.</string>
+    <string name="lockpassword_pin_contains_non_digits">Must contain only digits 0-9.</string>
 
     <!-- Error shown when in PIN mode and PIN has been used recently. Please keep this string short! -->
     <string name="lockpassword_pin_recently_used">Device administrator doesn\u2019t allow using a recent PIN.</string>
 
     <!-- Error shown when in PASSWORD mode and user enters an invalid character -->
-    <string name="lockpassword_illegal_character">Password contains an illegal character.</string>
+    <string name="lockpassword_illegal_character">This can\'t include an invalid character</string>
 
     <!-- Error shown when in PASSWORD mode and password is all digits -->
-    <string name="lockpassword_password_requires_alpha">Password must contain at least one letter.</string>
+    <string name="lockpassword_password_requires_alpha">Must contain at least one letter</string>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain any digits -->
-    <string name="lockpassword_password_requires_digit">Password must contain at least one digit.</string>
+    <string name="lockpassword_password_requires_digit">Must contain at least one digit</string>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain any symbols -->
-    <string name="lockpassword_password_requires_symbol">Password must contain at least one symbol.</string>
+    <string name="lockpassword_password_requires_symbol">Must contain at least one symbol</string>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of letters -->
     <plurals name="lockpassword_password_requires_letters">
-        <item quantity="one">Password must contain at least 1 letter.</item>
-        <item quantity="other">Password must contain at least %d letters.</item>
+        <item quantity="one">Must contain at least 1 letter</item>
+        <item quantity="other">Must contain at least %d letters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of lowercase letters -->
     <plurals name="lockpassword_password_requires_lowercase">
-        <item quantity="one">Password must contain at least 1 lowercase letter.</item>
-        <item quantity="other">Password must contain at least %d lowercase letters.</item>
+        <item quantity="one">Must contain at least 1 lowercase letter</item>
+        <item quantity="other">Must contain at least %d lowercase letters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of uppercase letters -->
     <plurals name="lockpassword_password_requires_uppercase">
-        <item quantity="one">Password must contain at least 1 uppercase letter.</item>
-        <item quantity="other">Password must contain at least %d uppercase letters.</item>
+        <item quantity="one">Must contain at least 1 uppercase letter</item>
+        <item quantity="other">Must contain at least %d uppercase letters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of numerical digits -->
     <plurals name="lockpassword_password_requires_numeric">
-        <item quantity="one">Password must contain at least 1 numerical digit.</item>
-        <item quantity="other">Password must contain at least %d numerical digits.</item>
+        <item quantity="one">Must contain at least 1 numerical digit</item>
+        <item quantity="other">Must contain at least %d numerical digits</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of special symbols -->
     <plurals name="lockpassword_password_requires_symbols">
-        <item quantity="one">Password must contain at least 1 special symbol.</item>
-        <item quantity="other">Password must contain at least %d special symbols.</item>
+        <item quantity="one">Must contain at least 1 special symbol</item>
+        <item quantity="other">Must contain at least %d special symbols</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-letter characters -->
     <plurals name="lockpassword_password_requires_nonletter">
-        <item quantity="one">Password must contain at least 1 non-letter character.</item>
-        <item quantity="other">Password must contain at least %d non-letter characters.</item>
+        <item quantity="one">Must contain at least 1 non-letter character</item>
+        <item quantity="other">Must contain at least %d non-letter characters</item>
     </plurals>
 
     <!-- Error shown when in PASSWORD mode and password has been used recently. Please keep this string short! -->
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
index 801f008..246f7d5 100644
--- a/src/com/android/settings/ChooseLockPassword.java
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -21,10 +21,11 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.InsetDrawable;
 import android.inputmethodservice.KeyboardView;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
@@ -32,13 +33,15 @@
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.Log;
+import android.view.inputmethod.EditorInfo;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
@@ -50,8 +53,18 @@
 import com.android.internal.widget.PasswordEntryKeyboardView;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.settings.notification.RedactionInterstitial;
+import com.android.settings.password.PasswordRequirementAdapter;
 import com.android.setupwizardlib.GlifLayout;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+
 public class ChooseLockPassword extends SettingsActivity {
     public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
     public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
@@ -149,7 +162,7 @@
     }
 
     public static class ChooseLockPasswordFragment extends InstrumentedFragment
-            implements OnClickListener, OnEditorActionListener,  TextWatcher,
+            implements OnClickListener, OnEditorActionListener, TextWatcher,
             SaveAndFinishWorker.Listener {
         private static final String KEY_FIRST_PIN = "first_pin";
         private static final String KEY_UI_STAGE = "ui_stage";
@@ -160,7 +173,7 @@
         private String mChosenPassword;
         private boolean mHasChallenge;
         private long mChallenge;
-        private TextView mPasswordEntry;
+        private EditText mPasswordEntry;
         private TextViewInputDisabler mPasswordEntryInputDisabler;
         private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
         private int mPasswordMaxLength = 16;
@@ -170,35 +183,53 @@
         private int mPasswordMinSymbols = 0;
         private int mPasswordMinNumeric = 0;
         private int mPasswordMinNonLetter = 0;
+        private int mUserId;
+        private boolean mHideDrawer = false;
+        /**
+         * Password requirements that we need to verify.
+         */
+        private int[] mPasswordRequirements;
+
         private LockPatternUtils mLockPatternUtils;
         private SaveAndFinishWorker mSaveAndFinishWorker;
         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
         private Stage mUiStage = Stage.Introduction;
+        private PasswordRequirementAdapter mPasswordRequirementAdapter;
 
         private TextView mHeaderText;
         private String mFirstPin;
+        private RecyclerView mPasswordRestrictionView;
         private KeyboardView mKeyboardView;
         private PasswordEntryKeyboardHelper mKeyboardHelper;
         private boolean mIsAlphaMode;
         private Button mCancelButton;
         private Button mNextButton;
+
         private static final int CONFIRM_EXISTING_REQUEST = 58;
         static final int RESULT_FINISHED = RESULT_FIRST_USER;
-        private static final long ERROR_MESSAGE_TIMEOUT = 3000;
-        private static final int MSG_SHOW_ERROR = 1;
 
-        private int mUserId;
-        private boolean mHideDrawer = false;
+        private static final int MIN_LETTER_IN_PASSWORD = 0;
+        private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1;
+        private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2;
+        private static final int MIN_SYMBOLS_IN_PASSWORD = 3;
+        private static final int MIN_NUMBER_IN_PASSWORD = 4;
+        private static final int MIN_NON_LETTER_IN_PASSWORD = 5;
 
-        private Handler mHandler = new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == MSG_SHOW_ERROR) {
-                    updateStage((Stage) msg.obj);
-                }
-            }
-        };
+        // Error code returned from {@link #validatePassword(String)}.
+        private static final int NO_ERROR = 0;
+        private static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
+        private static final int TOO_SHORT = 1 << 1;
+        private static final int TOO_LONG = 1 << 2;
+        private static final int CONTAIN_NON_DIGITS = 1 << 3;
+        private static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4;
+        private static final int RECENTLY_USED = 1 << 5;
+        private static final int NOT_ENOUGH_LETTER = 1 << 6;
+        private static final int NOT_ENOUGH_UPPER_CASE = 1 << 7;
+        private static final int NOT_ENOUGH_LOWER_CASE = 1 << 8;
+        private static final int NOT_ENOUGH_DIGITS = 1 << 9;
+        private static final int NOT_ENOUGH_SYMBOLS = 1 << 10;
+        private static final int NOT_ENOUGH_NON_LETTER = 1 << 11;
 
         /**
          * Keep track internally of where the user is in choosing a pattern.
@@ -243,33 +274,7 @@
             }
             // Only take this argument into account if it belongs to the current profile.
             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
-            mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
-                    mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality(
-                    mUserId));
-            mPasswordMinLength = Math.max(Math.max(
-                    LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
-                    intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
-                    mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
-            mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
-            mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
-                    mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
-                    mUserId));
-            mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
-                    mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
-                    mUserId));
-            mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
-                    mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
-                    mUserId));
-            mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
-                    mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
-                    mUserId));
-            mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
-                    mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
-                    mUserId));
-            mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
-                    mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
-                    mUserId));
-
+            processPasswordRequirements(intent);
             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
             mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
 
@@ -305,8 +310,12 @@
             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
+
+            setupPasswordRequirementsView(view);
+
             mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
-            mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
+            mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
+            mPasswordEntry = (EditText) view.findViewById(R.id.password_entry);
             mPasswordEntry.setOnEditorActionListener(this);
             mPasswordEntry.addTextChangedListener(this);
             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
@@ -356,6 +365,24 @@
                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
                         FRAGMENT_TAG_SAVE_AND_FINISH);
             }
+
+            // Workaround to show one password requirement below EditText when IME is shown.
+            // By adding an inset to the edit text background, we make the EditText occupy more
+            // vertical space, and the keyboard will then avoid hiding it. We have also set
+            // negative margin in the layout below in order to have them show in the correct
+            // position.
+            final int visibleVerticalSpaceBelowPassword =
+                    getResources().getDimensionPixelOffset(
+                        R.dimen.visible_vertical_space_below_password);
+            InsetDrawable drawable =
+                    new InsetDrawable(
+                    mPasswordEntry.getBackground(), 0, 0, 0, visibleVerticalSpaceBelowPassword);
+            mPasswordEntry.setBackgroundDrawable(drawable);
+            LinearLayout bottomContainer = (LinearLayout) view.findViewById(R.id.bottom_container);
+            LinearLayout.LayoutParams bottomContainerLp =
+                    (LinearLayout.LayoutParams) bottomContainer.getLayoutParams();
+            bottomContainerLp.setMargins(0, -visibleVerticalSpaceBelowPassword, 0, 0);
+
             if (activity instanceof SettingsActivity) {
                 final SettingsActivity sa = (SettingsActivity) activity;
                 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
@@ -366,6 +393,55 @@
             }
         }
 
+        private void setupPasswordRequirementsView(View view) {
+            // Construct passwordRequirements and requirementDescriptions.
+            List<Integer> passwordRequirements = new ArrayList<>();
+            List<String> requirementDescriptions = new ArrayList<>();
+            if (mPasswordMinUpperCase > 0) {
+                passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
+                requirementDescriptions.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
+                        mPasswordMinUpperCase));
+            }
+            if (mPasswordMinLowerCase > 0) {
+                passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
+                requirementDescriptions.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
+                        mPasswordMinLowerCase));
+            }
+            if (mPasswordMinLetters > 0) {
+                passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
+                requirementDescriptions.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
+                        mPasswordMinLetters));
+            }
+            if (mPasswordMinNumeric > 0) {
+                passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
+                requirementDescriptions.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
+                        mPasswordMinNumeric));
+            }
+            if (mPasswordMinSymbols > 0) {
+                passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
+                requirementDescriptions.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
+                        mPasswordMinSymbols));
+            }
+            if (mPasswordMinNonLetter > 0) {
+                passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
+                requirementDescriptions.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
+                        mPasswordMinNonLetter));
+            }
+            // Convert list to array.
+            mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
+            mPasswordRestrictionView =
+                    (RecyclerView) view.findViewById(R.id.password_requirements_view);
+            mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
+            mPasswordRequirementAdapter = new PasswordRequirementAdapter();
+            mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
+        }
+
         @Override
         protected int getMetricsCategory() {
             return MetricsEvent.CHOOSE_LOCK_PASSWORD;
@@ -384,11 +460,9 @@
 
         @Override
         public void onPause() {
-            mHandler.removeMessages(MSG_SHOW_ERROR);
             if (mSaveAndFinishWorker != null) {
                 mSaveAndFinishWorker.setListener(null);
             }
-
             super.onPause();
         }
 
@@ -434,21 +508,95 @@
         }
 
         /**
-         * Validates PIN and returns a message to display if PIN fails test.
-         * @param password the raw password the user typed in
-         * @return error message to show to user or null if password is OK
+         * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
+         *
+         * @param intent the incoming intent
          */
-        private String validatePassword(String password) {
+        private void processPasswordRequirements(Intent intent) {
+            final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
+            mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
+                    mRequestedQuality), dpmPasswordQuality);
+            mPasswordMinLength = Math.max(Math.max(
+                    LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
+                    intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
+                    mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
+            mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
+            mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
+                    mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
+                    mUserId));
+            mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
+                    mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
+                    mUserId));
+            mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
+                    mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
+                    mUserId));
+            mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
+                    mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
+                    mUserId));
+            mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
+                    mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
+                    mUserId));
+            mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
+                    mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
+                    mUserId));
+
+            // Modify the value based on dpm policy.
+            switch (dpmPasswordQuality) {
+                case PASSWORD_QUALITY_ALPHABETIC:
+                    if (mPasswordMinLetters == 0) {
+                        mPasswordMinLetters = 1;
+                    }
+                    break;
+                case PASSWORD_QUALITY_ALPHANUMERIC:
+                    if (mPasswordMinLetters == 0) {
+                        mPasswordMinLetters = 1;
+                    }
+                    if (mPasswordMinNumeric == 0) {
+                        mPasswordMinNumeric = 1;
+                    }
+                    break;
+                case PASSWORD_QUALITY_COMPLEX:
+                    // Reserve all the requirements.
+                    break;
+                default:
+                    mPasswordMinNumeric = 0;
+                    mPasswordMinLetters = 0;
+                    mPasswordMinUpperCase = 0;
+                    mPasswordMinLowerCase = 0;
+                    mPasswordMinSymbols = 0;
+                    mPasswordMinNonLetter = 0;
+            }
+        }
+
+        /**
+         * Validates PIN and returns the validation result.
+         *
+         * @param password the raw password the user typed in
+         * @return the validation result.
+         */
+        private int validatePassword(String password) {
+            int errorCode = NO_ERROR;
+
             if (password.length() < mPasswordMinLength) {
-                return getString(mIsAlphaMode ?
-                        R.string.lockpassword_password_too_short
-                        : R.string.lockpassword_pin_too_short, mPasswordMinLength);
+                errorCode |= TOO_SHORT;
+            } else if (password.length() > mPasswordMaxLength) {
+                errorCode |= TOO_LONG;
+            } else {
+                // The length requirements are fulfilled.
+                if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+                    // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
+                    final int sequence = LockPatternUtils.maxLengthSequence(password);
+                    if (sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
+                        errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
+                    }
+                }
+                // Is the password recently used?
+                if (mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
+                    errorCode |= RECENTLY_USED;
+                }
             }
-            if (password.length() > mPasswordMaxLength) {
-                return getString(mIsAlphaMode ?
-                        R.string.lockpassword_password_too_long
-                        : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1);
-            }
+
+            // Count different types of character.
             int letters = 0;
             int numbers = 0;
             int lowercase = 0;
@@ -459,7 +607,8 @@
                 char c = password.charAt(i);
                 // allow non control Latin-1 characters only
                 if (c < 32 || c > 127) {
-                    return getString(R.string.lockpassword_illegal_character);
+                    errorCode |= CONTAIN_INVALID_CHARACTERS;
+                    continue;
                 }
                 if (c >= '0' && c <= '9') {
                     numbers++;
@@ -475,63 +624,53 @@
                     nonletter++;
                 }
             }
-            if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
-                    || DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) {
+
+            // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless
+            // user finds some way to bring up soft keyboard.
+            if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC
+                    || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
                 if (letters > 0 || symbols > 0) {
-                    // This shouldn't be possible unless user finds some way to bring up
-                    // soft keyboard
-                    return getString(R.string.lockpassword_pin_contains_non_digits);
+                    errorCode |= CONTAIN_NON_DIGITS;
                 }
-                // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
-                final int sequence = LockPatternUtils.maxLengthSequence(password);
-                if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality
-                        && sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
-                    return getString(R.string.lockpassword_pin_no_sequential_digits);
-                }
-            } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
-                if (letters < mPasswordMinLetters) {
-                    return String.format(getResources().getQuantityString(
-                            R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
-                            mPasswordMinLetters);
-                } else if (numbers < mPasswordMinNumeric) {
-                    return String.format(getResources().getQuantityString(
-                            R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
-                            mPasswordMinNumeric);
-                } else if (lowercase < mPasswordMinLowerCase) {
-                    return String.format(getResources().getQuantityString(
-                            R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
-                            mPasswordMinLowerCase);
-                } else if (uppercase < mPasswordMinUpperCase) {
-                    return String.format(getResources().getQuantityString(
-                            R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
-                            mPasswordMinUpperCase);
-                } else if (symbols < mPasswordMinSymbols) {
-                    return String.format(getResources().getQuantityString(
-                            R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
-                            mPasswordMinSymbols);
-                } else if (nonletter < mPasswordMinNonLetter) {
-                    return String.format(getResources().getQuantityString(
-                            R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
-                            mPasswordMinNonLetter);
-                }
-            } else {
-                final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
-                        == mRequestedQuality;
-                final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
-                        == mRequestedQuality;
-                if ((alphabetic || alphanumeric) && letters == 0) {
-                    return getString(R.string.lockpassword_password_requires_alpha);
-                }
-                if (alphanumeric && numbers == 0) {
-                    return getString(R.string.lockpassword_password_requires_digit);
-                }
-            }
-            if(mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
-                return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
-                        : R.string.lockpassword_pin_recently_used);
             }
 
-            return null;
+            // Check the requirements one by one.
+            for (int i = 0; i < mPasswordRequirements.length; i++) {
+                int passwordRestriction = mPasswordRequirements[i];
+                switch (passwordRestriction) {
+                    case MIN_LETTER_IN_PASSWORD:
+                        if (letters < mPasswordMinLetters) {
+                            errorCode |= NOT_ENOUGH_LETTER;
+                        }
+                        break;
+                    case MIN_UPPER_LETTERS_IN_PASSWORD:
+                        if (uppercase < mPasswordMinUpperCase) {
+                            errorCode |= NOT_ENOUGH_UPPER_CASE;
+                        }
+                        break;
+                    case MIN_LOWER_LETTERS_IN_PASSWORD:
+                        if (lowercase < mPasswordMinLowerCase) {
+                            errorCode |= NOT_ENOUGH_LOWER_CASE;
+                        }
+                        break;
+                    case MIN_SYMBOLS_IN_PASSWORD:
+                        if (symbols < mPasswordMinSymbols) {
+                            errorCode |= NOT_ENOUGH_SYMBOLS;
+                        }
+                        break;
+                    case MIN_NUMBER_IN_PASSWORD:
+                        if (numbers < mPasswordMinNumeric) {
+                            errorCode |= NOT_ENOUGH_DIGITS;
+                        }
+                        break;
+                    case MIN_NON_LETTER_IN_PASSWORD:
+                        if (nonletter < mPasswordMinNonLetter) {
+                            errorCode |= NOT_ENOUGH_NON_LETTER;
+                        }
+                        break;
+                }
+            }
+            return errorCode;
         }
 
         public void handleNext() {
@@ -540,10 +679,8 @@
             if (TextUtils.isEmpty(mChosenPassword)) {
                 return;
             }
-            String errorMsg = null;
             if (mUiStage == Stage.Introduction) {
-                errorMsg = validatePassword(mChosenPassword);
-                if (errorMsg == null) {
+                if (validatePassword(mChosenPassword) == NO_ERROR) {
                     mFirstPin = mChosenPassword;
                     mPasswordEntry.setText("");
                     updateStage(Stage.NeedToConfirm);
@@ -559,9 +696,6 @@
                     updateStage(Stage.ConfirmWrong);
                 }
             }
-            if (errorMsg != null) {
-                showError(errorMsg, mUiStage);
-            }
         }
 
         protected void setNextEnabled(boolean enabled) {
@@ -584,14 +718,6 @@
             }
         }
 
-        private void showError(String msg, final Stage next) {
-            mHeaderText.setText(msg);
-            mHeaderText.announceForAccessibility(mHeaderText.getText());
-            Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
-            mHandler.removeMessages(MSG_SHOW_ERROR);
-            mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
-        }
-
         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
             // Check if this was the result of hitting the enter or "done" key
             if (actionId == EditorInfo.IME_NULL
@@ -604,6 +730,68 @@
         }
 
         /**
+         * @param errorCode error code returned from {@link #validatePassword(String)}.
+         * @return an array of messages describing the error, important messages come first.
+         */
+        private String[] convertErrorCodeToMessages(int errorCode) {
+            List<String> messages = new ArrayList<>();
+            if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
+                messages.add(getString(R.string.lockpassword_illegal_character));
+            }
+            if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
+                messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
+            }
+            if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
+                        mPasswordMinLetters));
+            }
+            if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
+                        mPasswordMinUpperCase));
+            }
+            if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
+                        mPasswordMinLowerCase));
+            }
+            if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
+                        mPasswordMinNumeric));
+            }
+            if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
+                        mPasswordMinSymbols));
+            }
+            if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
+                        mPasswordMinNonLetter));
+            }
+            if ((errorCode & TOO_SHORT) > 0) {
+                messages.add(getString(mIsAlphaMode ?
+                        R.string.lockpassword_password_too_short
+                        : R.string.lockpassword_pin_too_short, mPasswordMinLength));
+            }
+            if ((errorCode & TOO_LONG) > 0) {
+                messages.add(getString(mIsAlphaMode ?
+                        R.string.lockpassword_password_too_long
+                        : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1));
+            }
+            if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
+                messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
+            }
+            if ((errorCode & RECENTLY_USED) > 0) {
+                messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
+                        : R.string.lockpassword_pin_recently_used));
+            }
+            return messages.toArray(new String[0]);
+        }
+
+        /**
          * Update the hint based on current Stage and length of password entry
          */
         private void updateUi() {
@@ -611,29 +799,33 @@
             String password = mPasswordEntry.getText().toString();
             final int length = password.length();
             if (mUiStage == Stage.Introduction) {
-                if (length < mPasswordMinLength) {
-                    String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
-                            : R.string.lockpassword_pin_too_short, mPasswordMinLength);
-                    mHeaderText.setText(msg);
-                    setNextEnabled(false);
-                } else {
-                    String error = validatePassword(password);
-                    if (error != null) {
-                        mHeaderText.setText(error);
-                        setNextEnabled(false);
-                    } else {
-                        mHeaderText.setText(null);
-                        setNextEnabled(true);
-                    }
-                }
+                mPasswordRestrictionView.setVisibility(View.VISIBLE);
+                final int errorCode = validatePassword(password);
+                String[] messages = convertErrorCodeToMessages(errorCode);
+                // Update the fulfillment of requirements.
+                mPasswordRequirementAdapter.setRequirements(messages);
+                // Enable/Disable the next button accordingly.
+                setNextEnabled(errorCode == NO_ERROR);
             } else {
-                mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
+                // Hide password requirement view when we are just asking user to confirm the pw.
+                mPasswordRestrictionView.setVisibility(View.GONE);
+                setHeaderText(getString(
+                        mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint));
                 setNextEnabled(canInput && length > 0);
             }
             setNextText(mUiStage.buttonText);
             mPasswordEntryInputDisabler.setInputEnabled(canInput);
         }
 
+        private void setHeaderText(String text) {
+            // Only set the text if it is different than the existing one to avoid announcing again.
+            if (!TextUtils.isEmpty(mHeaderText.getText())
+                    && mHeaderText.getText().toString().equals(text)) {
+                return;
+            }
+            mHeaderText.setText(text);
+        }
+
         public void afterTextChanged(Editable s) {
             // Changing the text while error displayed resets to NeedToConfirm state
             if (mUiStage == Stage.ConfirmWrong) {
diff --git a/src/com/android/settings/password/PasswordRequirementAdapter.java b/src/com/android/settings/password/PasswordRequirementAdapter.java
new file mode 100644
index 0000000..dd4d10a
--- /dev/null
+++ b/src/com/android/settings/password/PasswordRequirementAdapter.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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.settings.password;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+import static com.android.settings.password.PasswordRequirementAdapter
+        .PasswordRequirementViewHolder;
+
+/**
+ * Used in {@link com.android.settings.ConfirmLockPassword} to show password requirements.
+ */
+public class PasswordRequirementAdapter extends
+        RecyclerView.Adapter<PasswordRequirementViewHolder> {
+    private String[] mRequirements;
+
+    public PasswordRequirementAdapter() {
+        setHasStableIds(true);
+    }
+
+    @Override
+    public PasswordRequirementViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.password_requirement_item, parent, false);
+        return new PasswordRequirementViewHolder(v);
+    }
+
+    @Override
+    public int getItemCount() {
+        return  mRequirements.length;
+    }
+
+    public void setRequirements(String[] requirements) {
+        mRequirements = requirements;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mRequirements[position].hashCode();
+    }
+
+    @Override
+    public void onBindViewHolder(PasswordRequirementViewHolder holder, int position) {
+        holder.mDescriptionText.setText(mRequirements[position]);
+    }
+
+    public static class PasswordRequirementViewHolder extends RecyclerView.ViewHolder {
+        private TextView mDescriptionText;
+
+        public PasswordRequirementViewHolder(View itemView) {
+            super(itemView);
+            mDescriptionText = (TextView) itemView;
+        }
+    }
+
+}