Move security pin pad to left half of screen

Change-Id: Ife323dfb782c369da814ab6829fdf072fd98d02e
Fixes: 74082771
Fixes: 73824490
Test: m -j CarSettingsRoboTests
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 93d7f8a..30feb98 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -123,6 +123,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".security.ChooseLockPinActivity"
+                  android:configChanges="orientation|keyboardHidden|screenSize">
+            <intent-filter>
+                <action android:name="android.car.settings.CHOOSE_LOCK_PIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <service android:name=".bluetooth.BluetoothPairingService" />
 
         <receiver android:name=".bluetooth.BluetoothPairingRequest">
diff --git a/res/drawable/ic_backspace.xml b/res/drawable/ic_backspace.xml
new file mode 100644
index 0000000..fb0bb24
--- /dev/null
+++ b/res/drawable/ic_backspace.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2018 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:fillColor="#FF000000"
+        android:pathData="M22,3H7C6.31,3 5.77,3.35 5.41,3.88l-5.04,7.57c-0.22,0.34 -0.22,0.77 0,1.11l5.04,7.56C5.77,20.64 6.31,21 7,21h15c1.1,0 2,-0.9 2,-2V5C24,3.9 23.1,3 22,3zM18.3,16.3L18.3,16.3c-0.39,0.39 -1.02,0.39 -1.41,0L14,13.41l-2.89,2.89c-0.39,0.39 -1.02,0.39 -1.41,0h0c-0.39,-0.39 -0.39,-1.02 0,-1.41L12.59,12L9.7,9.11c-0.39,-0.39 -0.39,-1.02 0,-1.41l0,0c0.39,-0.39 1.02,-0.39 1.41,0L14,10.59l2.89,-2.89c0.39,-0.39 1.02,-0.39 1.41,0v0c0.39,0.39 0.39,1.02 0,1.41L15.41,12l2.89,2.89C18.68,15.27 18.68,15.91 18.3,16.3z"/>
+</vector>
diff --git a/res/drawable/ic_done.xml b/res/drawable/ic_done.xml
new file mode 100644
index 0000000..3637853
--- /dev/null
+++ b/res/drawable/ic_done.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2018 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:fillColor="#FF000000"
+        android:pathData="M9,16.2l-3.5,-3.5a0.984,0.984 0,0 0,-1.4 0,0.984 0.984,0 0,0 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7a0.984,0.984 0,0 0,0 -1.4,0.984 0.984,0 0,0 -1.4,0L9,16.2z"/>
+</vector>
diff --git a/res/layout/choose_lock_password.xml b/res/layout/choose_lock_password.xml
index 67424ec..0b082be 100644
--- a/res/layout/choose_lock_password.xml
+++ b/res/layout/choose_lock_password.xml
@@ -32,20 +32,16 @@
         android:orientation="vertical"
         android:paddingTop="@dimen/car_primary_icon_size">
 
-        <ImageView
-            android:id="@+id/base_icon"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/car_primary_icon_size"
-            android:layout_marginBottom="@dimen/car_padding_2"
-            android:gravity="center"/>
-
         <TextView
             android:id="@+id/title_text"
-            android:gravity="center"
-            android:layout_width="match_parent"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginBottom="@dimen/car_padding_2"
-            android:textAppearance="@style/TextAppearance.Car.Title" />
+            android:textAppearance="@style/TextAppearance.Car.Title2"
+            android:text="@string/set_screen_lock"
+            android:drawableStart="@drawable/ic_lock"
+            android:drawablePadding="@dimen/lockscreen_title_drawable_padding"/>
 
         <TextView
             android:id="@+id/description_text"
@@ -53,23 +49,17 @@
             android:layout_height="wrap_content"
             android:layout_marginBottom="@dimen/car_padding_5"
             android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body2" />
+            android:textAppearance="@style/TextAppearance.Car.Body2"
+            android:text="@string/choose_lock_password_message"/>
     </LinearLayout>
 
-    <!-- End side: pin/password entry field -->
+    <!-- End side: password entry field -->
     <LinearLayout
         android:layout_height="wrap_content"
         android:layout_width="0dp"
         android:layout_weight="@integer/content_weight"
         android:orientation="vertical"
         android:paddingTop="@dimen/car_primary_icon_size">
-        <!--  header text -->
-        <TextView
-            android:id="@+id/header_text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.Car.Body1" />
 
         <EditText
             android:id="@+id/password_entry"
@@ -103,7 +93,7 @@
 
             <!-- left / top button: skip, or re-try -->
             <Button android:id="@+id/footerSecondaryButton"
-                    style="@style/SetupWizardButton.Negative"
+                    style="@style/Widget.Car.Button.Borderless.Colored"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:textSize="@dimen/car_action1_size"
@@ -112,7 +102,7 @@
 
             <!-- right / bottom button: confirm or ok -->
             <Button android:id="@+id/footerPrimaryButton"
-                    style="@style/SetupWizardButton.Positive"
+                    style="@style/Widget.Car.Button"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:textSize="@dimen/car_action1_size"
diff --git a/res/layout/choose_lock_pattern.xml b/res/layout/choose_lock_pattern.xml
index 09a07c4..adfb8db 100644
--- a/res/layout/choose_lock_pattern.xml
+++ b/res/layout/choose_lock_pattern.xml
@@ -86,7 +86,7 @@
 
             <!-- left / top button: skip, or re-try -->
             <Button android:id="@+id/footerSecondaryButton"
-                    style="@style/SetupWizardButton.Negative"
+                    style="@style/Widget.Car.Button.Borderless.Colored"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:textSize="@dimen/car_action1_size"
@@ -95,7 +95,7 @@
 
             <!-- right / bottom button: confirm or ok -->
             <Button android:id="@+id/footerPrimaryButton"
-                    style="@style/SetupWizardButton.Positive"
+                    style="@style/Widget.Car.Button"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:textSize="@dimen/car_action1_size"
diff --git a/res/layout/choose_lock_pin.xml b/res/layout/choose_lock_pin.xml
new file mode 100644
index 0000000..8c57112
--- /dev/null
+++ b/res/layout/choose_lock_pin.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2018 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="@dimen/car_margin"
+    android:layout_marginEnd="@dimen/car_margin"
+    android:orientation="horizontal"
+    android:paddingTop="@dimen/car_app_bar_height"
+    android:paddingBottom="@dimen/car_app_bar_height">
+
+    <!-- Start side: lock pattern -->
+    <FrameLayout
+        android:layout_weight="@integer/content_weight"
+        android:layout_width="0dp"
+        android:layout_height="match_parent">
+
+        <include layout="@layout/pin_pad"/>
+    </FrameLayout>
+
+    <!-- End side: pin entry field and messages -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="0dp"
+        android:layout_weight="@integer/content_weight"
+        android:orientation="vertical"
+        android:layout_marginTop="@dimen/pin_pad_end_padding_top">
+
+        <TextView
+            android:id="@+id/title_text"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/car_padding_2"
+            android:textAppearance="@style/TextAppearance.Car.Title2"
+            android:text="@string/set_screen_lock"
+            android:drawableStart="@drawable/ic_lock"
+            android:drawablePadding="@dimen/lockscreen_title_drawable_padding"/>
+
+        <TextView
+            android:id="@+id/description_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/car_padding_5"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance.Car.Body2"
+            android:text="@string/choose_lock_pin_message"/>
+
+        <EditText
+            android:id="@+id/password_entry"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance.Car.Body1"
+            android:maxLines="1"
+            android:inputType="none"
+            android:password="true"
+            android:cursorVisible="false"
+            android:focusable="false"/>
+
+        <!--  hint text -->
+        <TextView
+            android:id="@+id/hint_text"
+            android:text="@string/choose_lock_pin_hints"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textAppearance="@style/TextAppearance.Car.Body2" />
+
+        <!-- confirm / restart buttons -->
+        <LinearLayout
+            android:id="@+id/buttonContainer"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+            <!-- skip, or re-try -->
+            <Button android:id="@+id/footerSecondaryButton"
+                    style="@style/Widget.Car.Button.Borderless.Colored"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textSize="@dimen/car_action1_size"
+                    android:layout_marginEnd="@dimen/car_padding_4"
+                    android:text="@string/lockpattern_cancel_button_text" />
+
+            <!-- confirm or ok -->
+            <Button android:id="@+id/footerPrimaryButton"
+                    style="@style/Widget.Car.Button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textSize="@dimen/car_action1_size"
+                    android:text="@string/lockpattern_confirm_button_text" />
+
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/pin_pad.xml b/res/layout/pin_pad.xml
new file mode 100644
index 0000000..1dbc77b
--- /dev/null
+++ b/res/layout/pin_pad.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="@dimen/pin_pad_padding_top"
+    android:gravity="center"
+    android:columnCount="3">
+
+    <!-- Row 1 -->
+    <Button
+        android:id="@+id/key1"
+        style="@style/PinPadKey"
+        android:text="@string/one"
+        android:tag="1"/>
+    <Button
+        android:id="@+id/key2"
+        style="@style/PinPadKey"
+        android:text="@string/two"
+        android:tag="2"/>
+    <Button
+        android:id="@+id/key3"
+        style="@style/PinPadKey"
+        android:text="@string/three"
+        android:tag="3"/>
+
+    <!-- Row 2 -->
+    <Button
+        android:id="@+id/key4"
+        style="@style/PinPadKey"
+        android:text="@string/four"
+        android:tag="4"/>
+    <Button
+        android:id="@+id/key5"
+        style="@style/PinPadKey"
+        android:text="@string/five"
+        android:tag="5"/>
+    <Button
+        android:id="@+id/key6"
+        style="@style/PinPadKey"
+        android:text="@string/six"
+        android:tag="6"/>
+
+    <!-- Row 3 -->
+    <Button
+        android:id="@+id/key7"
+        style="@style/PinPadKey"
+        android:text="@string/seven"
+        android:tag="7"/>
+    <Button
+        android:id="@+id/key8"
+        style="@style/PinPadKey"
+        android:text="@string/eight"
+        android:tag="8"/>
+    <Button
+        android:id="@+id/key9"
+        style="@style/PinPadKey"
+        android:text="@string/nine"
+        android:tag="9"/>
+
+    <!-- Row 4 -->
+    <ImageButton
+        android:id="@+id/key_backspace"
+        style="@style/PinPadKey"
+        android:src="@drawable/ic_backspace"
+        android:contentDescription="@string/backspace_key"/>
+    <Button
+        android:id="@+id/key0"
+        style="@style/PinPadKey"
+        android:text="@string/zero"
+        android:tag="0"/>
+    <ImageButton
+        android:id="@+id/key_enter"
+        style="@style/PinPadKey"
+        android:src="@drawable/ic_done"
+        android:contentDescription="@string/enter_key"/>
+</GridLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3537274..cfc2d1d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -30,4 +30,13 @@
 
     <integer name="bluetooth_name_length">32</integer>
 
+    <dimen name="pin_pad_key_width">120dp</dimen>
+    <dimen name="pin_pad_key_height">80dp</dimen>
+    <dimen name="pin_pad_key_margin">12dp</dimen>
+    <dimen name="pin_pad_padding_top">20dp</dimen>
+    <dimen name="pin_pad_end_padding_top">48dp</dimen>
+    <dimen name="pin_pad_key_text_size">48sp</dimen>
+
+    <dimen name="lockscreen_title_drawable_padding">5dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f873183..096e697 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -558,4 +558,21 @@
     <string name="remove_button">Remove</string>
     <!-- Cancel button text [CHAR LIMIT=20] -->
     <string name="cancel">Cancel</string>
+
+    <!-- Accessibility description for the backspace key in the PIN pad [CHAR LIMIT=NONE] -->
+    <string name="backspace_key">Backspace key</string>
+    <!-- Accessibility description for the enter key in the PIN pad [CHAR LIMIT=NONE] -->
+    <string name="enter_key">Enter key</string>
+
+    <!-- These are the digits on the PIN pad -->
+    <string name="one" translatable="false">1</string>
+    <string name="two" translatable="false">2</string>
+    <string name="three" translatable="false">3</string>
+    <string name="four" translatable="false">4</string>
+    <string name="five" translatable="false">5</string>
+    <string name="six" translatable="false">6</string>
+    <string name="seven" translatable="false">7</string>
+    <string name="eight" translatable="false">8</string>
+    <string name="nine" translatable="false">9</string>
+    <string name="zero" translatable="false">0</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1fd5380..e15b73c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -81,4 +81,15 @@
         <item name="*android:successColor">@color/lock_pattern_success</item>
         <item name="*android:errorColor">?android:attr/colorError</item>
     </style>
+
+    <style name="PinPadKey" parent="@style/TextAppearance.Car.Key2">
+        <item name="android:layout_width">@dimen/pin_pad_key_width</item>
+        <item name="android:layout_height">@dimen/pin_pad_key_height</item>
+        <item name="android:layout_margin">@dimen/pin_pad_key_margin</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textSize">@dimen/pin_pad_key_text_size</item>
+        <item name="android:textColor">@android:color/black</item>
+        <item name="android:clickable">true</item>
+        <item name="android:background">@drawable/button_ripple_bg</item>
+    </style>
 </resources>
diff --git a/src/com/android/car/settings/security/ChooseLockPasswordActivity.java b/src/com/android/car/settings/security/ChooseLockPasswordActivity.java
index 063ed25..4c2eae8 100644
--- a/src/com/android/car/settings/security/ChooseLockPasswordActivity.java
+++ b/src/com/android/car/settings/security/ChooseLockPasswordActivity.java
@@ -16,470 +16,75 @@
 
 package com.android.car.settings.security;
 
+import android.annotation.IdRes;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.PasswordMetrics;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.support.annotation.StringRes;
 import android.support.annotation.VisibleForTesting;
-import android.support.v4.app.FragmentActivity;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
 
 import com.android.car.settings.R;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.TextViewInputDisabler;
 
-import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 
 /**
- * Activity for choosing a lock password/pin.
+ * Activity for choosing a lock password.
  */
-public class ChooseLockPasswordActivity extends FragmentActivity implements
-        OnEditorActionListener,
-        TextWatcher,
-        View.OnClickListener,
-        SaveChosenLockWorkerBase.Listener {
+public class ChooseLockPasswordActivity extends ChooseLockPasswordBaseActivity {
+
+    // Error code returned from validatePassword(String).
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int CONTAINS_INVALID_CHARACTERS = 1;
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int DOES_NOT_MATCH_PATTERN = 1 << 1;
+
     /**
      * Password must contain at least one number, one letter,
      * can not have white space, should be between 4 to 8 characters.
      */
-    public static final String PASSWORD_PATTERN =
-            "^(?=.*[0-9])(?=.*[a-zA-Z])(?=\\S+$).{4,8}$";
+    private static final String PASSWORD_PATTERN = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=\\S+$).{4,8}$";
 
-    protected int mUserId;
-    protected Stage mUiStage = Stage.Introduction;
+    // Allow non-control Latin-1 characters only.
+    private static final String VALID_CHAR_PATTERN = "^[\\x20-\\x7F]*$";
 
-    // Error code returned from validatePassword(String).
-    @VisibleForTesting
-    static final int NO_ERROR = 0;
-    @VisibleForTesting
-    static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
-    @VisibleForTesting
-    static final int CONTAIN_NON_DIGITS = 1 << 1;
-    @VisibleForTesting
-    static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 2;
-    @VisibleForTesting
-    static final int DO_NOT_MATCH_PATTERN = 1 << 3;
-    @VisibleForTesting
-    static final int RECENTLY_USED = 1 << 4;
-    @VisibleForTesting
-    static final int BLACKLISTED = 1 << 5;
 
-    private static final String TAG = "ChooseLockPassword";
-    private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
-
-    private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-    private boolean mIsAlphaMode;
-    private String mEnteredPassword;
-    private String mCurrentPassword;
-    private EditText mPasswordEntry;
-    private TextViewInputDisabler mPasswordEntryInputDisabler;
-
-    private SaveLockPasswordWorker mSaveLockPasswordWorker;
-
-    private String mFirstPassword;
-
-    private TextView mDescriptionMessage;
-    private TextView mHintMessage;
-    private Button mSecondaryButton;
-    private Button mPrimaryButton;
-
-    private TextChangedHandler mTextChangedHandler;
-
-    // Keep track internally of where the user is in choosing a password.
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    protected enum Stage {
-        Introduction(
-                R.string.choose_lock_password_hints,
-                R.string.choose_lock_pin_hints,
-                R.string.continue_button_text,
-                R.string.lockpassword_cancel_label),
-
-        PasswordInvalid(
-                R.string.lockpassword_invalid_password,
-                R.string.lockpin_invalid_pin,
-                R.string.continue_button_text,
-                R.string.lockpassword_clear_label),
-
-        NeedToConfirm(
-                R.string.confirm_your_password_header,
-                R.string.confirm_your_pin_header,
-                R.string.lockpassword_confirm_label,
-                R.string.lockpassword_cancel_label),
-
-        ConfirmWrong(
-                R.string.confirm_passwords_dont_match,
-                R.string.confirm_pins_dont_match,
-                R.string.continue_button_text,
-                R.string.lockpassword_cancel_label),
-
-        SaveFailure(
-                R.string.error_saving_password,
-                R.string.error_saving_lockpin,
-                R.string.lockscreen_retry_button_text,
-                R.string.lockpassword_cancel_label);
-
-        public final int alphaHint;
-        public final int numericHint;
-        public final int primaryButtonText;
-        public final int secondaryButtonText;
-
-        Stage(int hintInAlpha,
-                int hintInNumeric,
-                int primaryButtonText,
-                int secondaryButtonText) {
-            this.alphaHint = hintInAlpha;
-            this.numericHint = hintInNumeric;
-            this.primaryButtonText = primaryButtonText;
-            this.secondaryButtonText = secondaryButtonText;
-        }
-
-        @StringRes
-        public int getHint(boolean isAlpha) {
-            if (isAlpha) {
-                return alphaHint;
-            } else {
-                return numericHint;
-            }
-        }
+    @Override
+    @IdRes
+    protected int getLayoutResId() {
+        return R.layout.choose_lock_password;
     }
 
     @Override
-    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-
+    protected int getPasswordQuality() {
+        return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
     }
 
     @Override
-    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
-
-    }
-
-    @Override
-    public void afterTextChanged(Editable editable) {
-        // Changing the text while error displayed resets to NeedToConfirm state
-        if (mUiStage == Stage.ConfirmWrong) {
-            mUiStage = Stage.NeedToConfirm;
-        }
-        // Schedule the UI update.
-        mTextChangedHandler.notifyAfterTextChanged();
-    }
-
-    @Override
-    public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
-        // Check if this was the result of hitting the enter or "done" key
-        if (actionId == EditorInfo.IME_NULL
-                || actionId == EditorInfo.IME_ACTION_DONE
-                || actionId == EditorInfo.IME_ACTION_NEXT) {
-            handlePrimaryButtonClick();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (view == mPrimaryButton) {
-            handlePrimaryButtonClick();
-        } else if (view == mSecondaryButton) {
-            handleSecondaryButtonClick();
-        }
-    }
-
-    @Override
-    public void onChosenLockSaveFinished(Intent data) {
-        boolean isSaveSuccessful =
-                data.getBooleanExtra(SaveChosenLockWorkerBase.EXTRA_KEY_SUCCESS, false);
-        if (isSaveSuccessful) {
-            setResult(RESULT_OK, data);
-            finish();
-        } else {
-            mSaveLockPasswordWorker = null;  // Allow new worker to be created
-            updateStage(Stage.SaveFailure);
-        }
-    }
-
-    /**
-     * Validates PIN/Password and returns the validation result.
-     *
-     * @param password the raw password the user typed in
-     * @return the validation result.
-     */
-    public int validatePassword(String password) {
+    protected int validatePassword(String password) {
         int errorCode = NO_ERROR;
 
-        final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
+        if (!password.matches(VALID_CHAR_PATTERN)) {
+            errorCode |= CONTAINS_INVALID_CHARACTERS;
+        }
 
-        // 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 == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-                || mRequestedQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-            if (metrics.letters > 0 || metrics.symbols > 0) {
-                errorCode |= CONTAIN_NON_DIGITS;
-            }
-
-            if (mRequestedQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) {
-                // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
-                final int sequence = PasswordMetrics.maxLengthSequence(password);
-                if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
-                    errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
-                }
-            }
-        } else {
-            // Allow non-control Latin-1 characters only.
-            for (int i = 0; i < password.length(); i++) {
-                char c = password.charAt(i);
-                if (c < 32 || c > 127) {
-                    errorCode |= CONTAIN_INVALID_CHARACTERS;
-                    break;
-                }
-            }
-            if (!password.matches(PASSWORD_PATTERN)) {
-                errorCode |= DO_NOT_MATCH_PATTERN;
-            }
+        if (!password.matches(PASSWORD_PATTERN)) {
+            errorCode |= DOES_NOT_MATCH_PATTERN;
         }
 
         return errorCode;
     }
 
-    @VisibleForTesting
-    void setPasswordQuality(int passwordQuality) {
-        mRequestedQuality = passwordQuality;
-    }
-
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-        int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
-                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
-        setPasswordQuality(quality);
-        setContentView(R.layout.choose_lock_password);
-        ImageView iconImg = (ImageView) findViewById(R.id.base_icon);
-        iconImg.setImageResource(R.drawable.ic_lock);
+    protected String[] convertErrorCodeToMessages(int errorCode) {
+        List<String> messages = new LinkedList<>();
 
-        mTextChangedHandler = new TextChangedHandler();
-        mUserId = UserHandle.myUserId();
-
-        mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
-                || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
-                || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
-
-        mPasswordEntry = (EditText) findViewById(R.id.password_entry);
-        mPasswordEntry.setOnEditorActionListener(this);
-        mPasswordEntry.addTextChangedListener(this);
-        mPasswordEntry.requestFocus();
-        mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
-
-        int currentType = mPasswordEntry.getInputType();
-        mPasswordEntry.setInputType(mIsAlphaMode ? currentType
-                : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
-
-        TextView titleText = (TextView) findViewById(R.id.title_text);
-        titleText.setText(getString(R.string.set_screen_lock));
-
-        mDescriptionMessage = (TextView) findViewById(R.id.description_text);
-        mDescriptionMessage.setText(getString(
-                mIsAlphaMode ? R.string.choose_lock_password_message
-                        : R.string.choose_lock_pin_message));
-
-        mHintMessage = (TextView) findViewById(R.id.hint_text);
-        mHintMessage.setText(getString(
-                mIsAlphaMode ? R.string.choose_lock_password_hints
-                        : R.string.choose_lock_pin_hints));
-
-        mPrimaryButton = (Button) findViewById(R.id.footerPrimaryButton);
-        mPrimaryButton.setOnClickListener(this);
-        mSecondaryButton = (Button) findViewById(R.id.footerSecondaryButton);
-        mSecondaryButton.setOnClickListener(this);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (mSaveLockPasswordWorker != null) {
-            mSaveLockPasswordWorker.setListener(null);
-        }
-    }
-
-    private void setPrimaryButtonEnabled(boolean enabled) {
-        mPrimaryButton.setEnabled(enabled);
-    }
-
-    private void setPrimaryButtonTextId(int textId) {
-        mPrimaryButton.setText(textId);
-    }
-
-    private void setSecondaryButtonEnabled(boolean enabled) {
-        mSecondaryButton.setEnabled(enabled);
-    }
-
-    private void setSecondaryButtonTextId(int textId) {
-        mSecondaryButton.setText(textId);
-    }
-
-    // Updates display message and proceed to next step according to the different text on
-    // the secondary button.
-    private void handleSecondaryButtonClick() {
-        if (mSaveLockPasswordWorker != null || TextUtils.isEmpty(mEnteredPassword)) {
-            finish();
-            return;
-        }
-
-        if (mUiStage.secondaryButtonText == R.string.lockpassword_clear_label) {
-            mPasswordEntry.setText("");
-            mUiStage = Stage.Introduction;
-            setSecondaryButtonTextId(mUiStage.secondaryButtonText);
-        } else {
-            finish();
-        }
-    }
-
-    // Updates display message and proceed to next step according to the different text on
-    // the primary button.
-    private void handlePrimaryButtonClick() {
-        mEnteredPassword = mPasswordEntry.getText().toString();
-        if (mSaveLockPasswordWorker != null || TextUtils.isEmpty(mEnteredPassword)) {
-            return;
-        }
-
-        switch(mUiStage) {
-            case Introduction:
-                if (validatePassword(mEnteredPassword) == NO_ERROR) {
-                    mFirstPassword = mEnteredPassword;
-                    mPasswordEntry.setText("");
-                    updateStage(Stage.NeedToConfirm);
-                } else {
-                    updateStage(Stage.PasswordInvalid);
-                }
-                break;
-            case NeedToConfirm:
-            case SaveFailure:
-                if (mFirstPassword.equals(mEnteredPassword)) {
-                    startSaveAndFinish();
-                } else {
-                    CharSequence tmp = mPasswordEntry.getText();
-                    if (tmp != null) {
-                        Selection.setSelection((Spannable) tmp, 0, tmp.length());
-                    }
-                    updateStage(Stage.ConfirmWrong);
-                }
-                break;
-            default:
-                // Do nothing.
-        }
-    }
-
-    // Starts an async task to save the chosen password.
-    private void startSaveAndFinish() {
-        if (mSaveLockPasswordWorker != null) {
-            Log.v(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
-            return;
-        }
-
-        mPasswordEntryInputDisabler.setInputEnabled(false);
-        setPrimaryButtonEnabled(false);
-
-        mSaveLockPasswordWorker = new SaveLockPasswordWorker();
-        mSaveLockPasswordWorker.setListener(this);
-
-        getFragmentManager().beginTransaction().add(mSaveLockPasswordWorker,
-                FRAGMENT_TAG_SAVE_AND_FINISH).commit();
-        getFragmentManager().executePendingTransactions();
-
-        mSaveLockPasswordWorker.start(new LockPatternUtils(this),
-                mEnteredPassword, mCurrentPassword, mRequestedQuality, mUserId);
-    }
-
-    // Updates the hint based on current Stage and length of password entry
-    private void updateUi() {
-        boolean inputAllowed = mSaveLockPasswordWorker == null;
-
-        if (mUiStage == Stage.Introduction) {
-            final int errorCode = validatePassword(mEnteredPassword);
-            String[] messages = convertErrorCodeToMessages(errorCode);
-            // Update the fulfillment of requirements.
-            mHintMessage.setText(messages.toString());
-            // Enable/Disable the next button accordingly.
-            setPrimaryButtonEnabled(errorCode == NO_ERROR);
-        } else {
-            mHintMessage.setText(getString(mUiStage.getHint(mIsAlphaMode)));
-            boolean hasPassword = mEnteredPassword == "" ? false : mEnteredPassword.length() > 0;
-            setPrimaryButtonEnabled(inputAllowed && hasPassword);
-            setSecondaryButtonEnabled(inputAllowed && hasPassword);
-        }
-
-        setPrimaryButtonTextId(mUiStage.primaryButtonText);
-        setSecondaryButtonTextId(mUiStage.secondaryButtonText);
-        mPasswordEntryInputDisabler.setInputEnabled(inputAllowed);
-    }
-
-    @VisibleForTesting
-    void updateStage(Stage stage) {
-        mUiStage = stage;
-        updateUi();
-    }
-
-    class TextChangedHandler extends Handler {
-        private static final int ON_TEXT_CHANGED = 1;
-        private static final int DELAY_IN_MILLISECOND = 100;
-
-        /**
-         * With the introduction of delay, we batch processing the text changed event to reduce
-         * unnecessary UI updates.
-         */
-        private void notifyAfterTextChanged() {
-            removeMessages(ON_TEXT_CHANGED);
-            sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-        }
-    }
-
-    // Converts error code from validatePassword to an array of message,
-    // describing the error, important message comes first.
-    private String[] convertErrorCodeToMessages(int errorCode) {
-        List<String> messages = new ArrayList<>();
-        if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
+        if ((errorCode & CONTAINS_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 & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
-            messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
-        }
-        if ((errorCode & DO_NOT_MATCH_PATTERN) > 0) {
+
+        if ((errorCode & DOES_NOT_MATCH_PATTERN) > 0) {
             messages.add(getString(R.string.lockpassword_invalid_password));
         }
-        if ((errorCode & RECENTLY_USED) > 0) {
-            messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
-                    : R.string.lockpassword_pin_recently_used));
-        }
-        if ((errorCode & BLACKLISTED) > 0) {
-            messages.add(getString((mIsAlphaMode)
-                    ? R.string.lockpassword_password_blacklisted_by_admin
-                    : R.string.lockpassword_pin_blacklisted_by_admin));
-        }
-        return messages.toArray(new String[0]);
+
+        return messages.toArray(new String[messages.size()]);
     }
 }
diff --git a/src/com/android/car/settings/security/ChooseLockPasswordBaseActivity.java b/src/com/android/car/settings/security/ChooseLockPasswordBaseActivity.java
new file mode 100644
index 0000000..5e259c0
--- /dev/null
+++ b/src/com/android/car/settings/security/ChooseLockPasswordBaseActivity.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.support.annotation.StringRes;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.FragmentActivity;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.TextViewInputDisabler;
+
+/**
+ * Abstract base Activity for choosing a lock password/pin.
+ */
+public abstract class ChooseLockPasswordBaseActivity extends FragmentActivity implements
+        SaveChosenLockWorkerBase.Listener {
+
+    protected int mUserId;
+    protected Stage mUiStage = Stage.Introduction;
+
+    // Error code returned from validatePassword(String).
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int NO_ERROR = 0;
+
+    private static final Logger LOG = new Logger(ChooseLockPasswordBaseActivity.class);
+
+    private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
+
+    private boolean mIsAlphaMode;
+    private String mEnteredPassword;
+    private String mCurrentPassword;
+    private TextViewInputDisabler mPasswordEntryInputDisabler;
+
+    private SaveLockPasswordWorker mSaveLockPasswordWorker;
+
+    private String mFirstPassword;
+
+    private TextView mDescriptionMessage;
+    private TextView mHintMessage;
+    private Button mSecondaryButton;
+    private Button mPrimaryButton;
+
+    private TextChangedHandler mTextChangedHandler;
+
+    private EditText mPasswordEntry;
+
+    // Keep track internally of where the user is in choosing a password.
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    protected enum Stage {
+        Introduction(
+                R.string.choose_lock_password_hints,
+                R.string.choose_lock_pin_hints,
+                R.string.continue_button_text,
+                R.string.lockpassword_cancel_label),
+
+        PasswordInvalid(
+                R.string.lockpassword_invalid_password,
+                R.string.lockpin_invalid_pin,
+                R.string.continue_button_text,
+                R.string.lockpassword_clear_label),
+
+        NeedToConfirm(
+                R.string.confirm_your_password_header,
+                R.string.confirm_your_pin_header,
+                R.string.lockpassword_confirm_label,
+                R.string.lockpassword_cancel_label),
+
+        ConfirmWrong(
+                R.string.confirm_passwords_dont_match,
+                R.string.confirm_pins_dont_match,
+                R.string.continue_button_text,
+                R.string.lockpassword_cancel_label),
+
+        SaveFailure(
+                R.string.error_saving_password,
+                R.string.error_saving_lockpin,
+                R.string.lockscreen_retry_button_text,
+                R.string.lockpassword_cancel_label);
+
+        public final int alphaHint;
+        public final int numericHint;
+        public final int primaryButtonText;
+        public final int secondaryButtonText;
+
+        Stage(int hintInAlpha,
+                int hintInNumeric,
+                int primaryButtonText,
+                int secondaryButtonText) {
+            this.alphaHint = hintInAlpha;
+            this.numericHint = hintInNumeric;
+            this.primaryButtonText = primaryButtonText;
+            this.secondaryButtonText = secondaryButtonText;
+        }
+
+        @StringRes
+        public int getHint(boolean isAlpha) {
+            if (isAlpha) {
+                return alphaHint;
+            } else {
+                return numericHint;
+            }
+        }
+    }
+
+    /**
+     * Returns the resource Id of the layout
+     */
+    @IdRes
+    protected abstract int getLayoutResId();
+
+    /**
+     * Returns one of the values defined in {@link DevicePolicyManager}
+     */
+    protected abstract int getPasswordQuality();
+
+    @Override
+    public void onChosenLockSaveFinished(Intent data) {
+        boolean isSaveSuccessful =
+                data.getBooleanExtra(SaveChosenLockWorkerBase.EXTRA_KEY_SUCCESS, false);
+        if (isSaveSuccessful) {
+            setResult(RESULT_OK, data);
+            finish();
+        } else {
+            mSaveLockPasswordWorker = null;  // Allow new worker to be created
+            updateStage(Stage.SaveFailure);
+        }
+    }
+
+    /**
+     * Validates PIN/Password and returns the validation result.
+     *
+     * @param password the raw password the user typed in
+     * @return the validation result.
+     */
+    protected int validatePassword(String password) {
+        return NO_ERROR;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getLayoutResId());
+
+        mTextChangedHandler = new TextChangedHandler();
+        mUserId = UserHandle.myUserId();
+
+        int passwordQuality = getPasswordQuality();
+        mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == passwordQuality
+                || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == passwordQuality
+                || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == passwordQuality;
+
+        mPasswordEntry = (EditText) findViewById(R.id.password_entry);
+        mPasswordEntry.setOnEditorActionListener((textView, actionId, keyEvent) -> {
+            // Check if this was the result of hitting the enter or "done" key
+            if (actionId == EditorInfo.IME_NULL
+                    || actionId == EditorInfo.IME_ACTION_DONE
+                    || actionId == EditorInfo.IME_ACTION_NEXT) {
+                handlePrimaryButtonClick(textView);
+                return true;
+            }
+            return false;
+        });
+
+        mPasswordEntry.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                // Changing the text while error displayed resets to NeedToConfirm state
+                if (mUiStage == Stage.ConfirmWrong) {
+                    mUiStage = Stage.NeedToConfirm;
+                }
+                // Schedule the UI update.
+                mTextChangedHandler.notifyAfterTextChanged();
+            }
+        });
+
+        mPasswordEntry.requestFocus();
+        mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
+
+        mDescriptionMessage = (TextView) findViewById(R.id.description_text);
+        mHintMessage = (TextView) findViewById(R.id.hint_text);
+
+        mPrimaryButton = (Button) findViewById(R.id.footerPrimaryButton);
+        mPrimaryButton.setOnClickListener(this::handlePrimaryButtonClick);
+        mSecondaryButton = (Button) findViewById(R.id.footerSecondaryButton);
+        mSecondaryButton.setOnClickListener(this::handleSecondaryButtonClick);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mSaveLockPasswordWorker != null) {
+            mSaveLockPasswordWorker.setListener(null);
+        }
+    }
+
+    protected final void appendToPasswordEntry(String text) {
+        mPasswordEntry.append(text);
+    }
+
+    protected final void setPasswordEntry(String text) {
+        mPasswordEntry.setText(text);
+    }
+
+    protected final String getPasswordEntry() {
+        return mPasswordEntry.getText().toString();
+    }
+
+    private void setPrimaryButtonEnabled(boolean enabled) {
+        mPrimaryButton.setEnabled(enabled);
+    }
+
+    private void setPrimaryButtonTextId(int textId) {
+        mPrimaryButton.setText(textId);
+    }
+
+    private void setSecondaryButtonEnabled(boolean enabled) {
+        mSecondaryButton.setEnabled(enabled);
+    }
+
+    private void setSecondaryButtonTextId(int textId) {
+        mSecondaryButton.setText(textId);
+    }
+
+    // Updates display message and proceed to next step according to the different text on
+    // the secondary button.
+    protected void handleSecondaryButtonClick(View unused) {
+        if (mSaveLockPasswordWorker != null || TextUtils.isEmpty(mEnteredPassword)) {
+            finish();
+            return;
+        }
+
+        if (mUiStage.secondaryButtonText == R.string.lockpassword_clear_label) {
+            mPasswordEntry.setText("");
+            mUiStage = Stage.Introduction;
+            setSecondaryButtonTextId(mUiStage.secondaryButtonText);
+        } else {
+            finish();
+        }
+    }
+
+    // Updates display message and proceed to next step according to the different text on
+    // the primary button.
+    protected void handlePrimaryButtonClick(View unused) {
+        mEnteredPassword = mPasswordEntry.getText().toString();
+        if (mSaveLockPasswordWorker != null || TextUtils.isEmpty(mEnteredPassword)) {
+            return;
+        }
+
+        switch(mUiStage) {
+            case Introduction:
+                if (validatePassword(mEnteredPassword) == NO_ERROR) {
+                    mFirstPassword = mEnteredPassword;
+                    mPasswordEntry.setText("");
+                    updateStage(Stage.NeedToConfirm);
+                } else {
+                    updateStage(Stage.PasswordInvalid);
+                }
+                break;
+            case NeedToConfirm:
+            case SaveFailure:
+                if (mFirstPassword.equals(mEnteredPassword)) {
+                    startSaveAndFinish();
+                } else {
+                    CharSequence tmp = mPasswordEntry.getText();
+                    if (tmp != null) {
+                        Selection.setSelection((Spannable) tmp, 0, tmp.length());
+                    }
+                    updateStage(Stage.ConfirmWrong);
+                }
+                break;
+            default:
+                // Do nothing.
+        }
+    }
+
+    // Starts an async task to save the chosen password.
+    private void startSaveAndFinish() {
+        if (mSaveLockPasswordWorker != null) {
+            LOG.v("startSaveAndFinish with an existing SaveAndFinishWorker.");
+            return;
+        }
+
+        mPasswordEntryInputDisabler.setInputEnabled(false);
+        setPrimaryButtonEnabled(false);
+
+        mSaveLockPasswordWorker = new SaveLockPasswordWorker();
+        mSaveLockPasswordWorker.setListener(this);
+
+        getFragmentManager().beginTransaction().add(mSaveLockPasswordWorker,
+                FRAGMENT_TAG_SAVE_AND_FINISH).commit();
+        getFragmentManager().executePendingTransactions();
+
+        mSaveLockPasswordWorker.start(new LockPatternUtils(this),
+                mEnteredPassword, mCurrentPassword, getPasswordQuality(), mUserId);
+    }
+
+    // Updates the hint based on current Stage and length of password entry
+    private void updateUi() {
+        boolean inputAllowed = mSaveLockPasswordWorker == null;
+
+        if (mUiStage == Stage.Introduction) {
+            final int errorCode = validatePassword(mEnteredPassword);
+            String[] messages = convertErrorCodeToMessages(errorCode);
+            // Update the fulfillment of requirements.
+            mHintMessage.setText(messages.toString());
+            // Enable/Disable the next button accordingly.
+            setPrimaryButtonEnabled(errorCode == NO_ERROR);
+        } else {
+            mHintMessage.setText(getString(mUiStage.getHint(mIsAlphaMode)));
+            boolean hasPassword = !mEnteredPassword.isEmpty();
+            setPrimaryButtonEnabled(inputAllowed && hasPassword);
+            setSecondaryButtonEnabled(inputAllowed && hasPassword);
+        }
+
+        setPrimaryButtonTextId(mUiStage.primaryButtonText);
+        setSecondaryButtonTextId(mUiStage.secondaryButtonText);
+        mPasswordEntryInputDisabler.setInputEnabled(inputAllowed);
+    }
+
+    @VisibleForTesting
+    void updateStage(Stage stage) {
+        mUiStage = stage;
+        updateUi();
+    }
+
+    /**
+     * Handler that batches text changed events
+     */
+    class TextChangedHandler extends Handler {
+        private static final int ON_TEXT_CHANGED = 1;
+        private static final int DELAY_IN_MILLISECOND = 100;
+
+        /**
+         * With the introduction of delay, we batch processing the text changed event to reduce
+         * unnecessary UI updates.
+         */
+        private void notifyAfterTextChanged() {
+            removeMessages(ON_TEXT_CHANGED);
+            sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+        }
+    }
+
+    // Converts error code from validatePassword to an array of message,
+    // describing the error, important message comes first.
+    @NonNull
+    protected String[] convertErrorCodeToMessages(int errorCode) {
+        return new String[0];
+    }
+}
diff --git a/src/com/android/car/settings/security/ChooseLockPinActivity.java b/src/com/android/car/settings/security/ChooseLockPinActivity.java
new file mode 100644
index 0000000..8fd899e
--- /dev/null
+++ b/src/com/android/car/settings/security/ChooseLockPinActivity.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.annotation.IdRes;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.PasswordMetrics;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.car.settings.R;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Activity for choosing a lock pin.
+ */
+public class ChooseLockPinActivity extends ChooseLockPasswordBaseActivity {
+    private static final int MIN_PIN_LENGTH = 4;
+
+    // View Ids used to set onClick listener
+    private static final int[] PIN_PAD_KEYS = { R.id.key0, R.id.key1, R.id.key2, R.id.key3,
+            R.id.key4, R.id.key5, R.id.key6, R.id.key7, R.id.key8, R.id.key9 };
+
+    // Error code returned from validatePassword
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int CONTAINS_NON_DIGITS = 1;
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int CONTAINS_SEQUENTIAL_DIGITS = 1 << 1;
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int TOO_FEW_DIGITS = 1 << 2;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        View backspace = findViewById(R.id.key_backspace);
+        backspace.setOnClickListener(v -> {
+            onBackspaceClick();
+        });
+
+        View enter = findViewById(R.id.key_enter);
+        enter.setOnClickListener(view -> {
+            handlePrimaryButtonClick(view);
+        });
+
+        for (int keyId : PIN_PAD_KEYS) {
+            TextView key = findViewById(keyId);
+            String digit = key.getTag().toString();
+            key.setOnClickListener(v -> appendToPasswordEntry(digit));
+        }
+    }
+
+    @Override
+    @IdRes
+    protected int getLayoutResId() {
+        return R.layout.choose_lock_pin;
+    }
+
+    @Override
+    protected int getPasswordQuality() {
+        return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+    }
+
+    @Override
+    protected int validatePassword(String password) {
+        int errorCode = NO_ERROR;
+
+        PasswordMetrics metrics = PasswordMetrics.computeForPassword(password);
+        int passwordQuality = getPasswordQuality();
+
+        if (metrics.length < MIN_PIN_LENGTH) {
+            errorCode |= TOO_FEW_DIGITS;
+        }
+
+        if (metrics.letters > 0 || metrics.symbols > 0) {
+            errorCode |= CONTAINS_NON_DIGITS;
+        }
+
+        if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+            // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
+            int sequence = PasswordMetrics.maxLengthSequence(password);
+            if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) {
+                errorCode |= CONTAINS_SEQUENTIAL_DIGITS;
+            }
+        }
+
+        return errorCode;
+    }
+
+    @Override
+    protected String[] convertErrorCodeToMessages(int errorCode) {
+        List<String> messages = new LinkedList<>();
+
+        if ((errorCode & CONTAINS_NON_DIGITS) > 0) {
+            messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
+        }
+
+        if ((errorCode & CONTAINS_SEQUENTIAL_DIGITS) > 0) {
+            messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
+        }
+
+        if ((errorCode & TOO_FEW_DIGITS) > 0) {
+            messages.add(getString(R.string.lockpin_invalid_pin));
+        }
+
+        return messages.toArray(new String[messages.size()]);
+    }
+
+    private void onBackspaceClick() {
+        String pin = getPasswordEntry();
+        if (pin.length() > 0) {
+            setPasswordEntry(pin.substring(0, pin.length() - 1));
+        }
+    }
+}
diff --git a/src/com/android/car/settings/security/ChooseLockTypeFragment.java b/src/com/android/car/settings/security/ChooseLockTypeFragment.java
index 686f562..8ead26e 100644
--- a/src/com/android/car/settings/security/ChooseLockTypeFragment.java
+++ b/src/com/android/car/settings/security/ChooseLockTypeFragment.java
@@ -16,20 +16,17 @@
 
 package com.android.car.settings.security;
 
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 
 import com.android.car.settings.R;
 import com.android.car.settings.common.ListItemSettingsFragment;
-import com.android.internal.widget.LockPatternUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.car.widget.ListItemProvider;
 import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemProvider;
 import androidx.car.widget.TextListItem;
 
 /**
@@ -103,15 +100,11 @@
 
     private void startChooseLockPasswordActivity() {
         Intent intent = new Intent(getContext(), ChooseLockPasswordActivity.class);
-        intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
-                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
         startActivityForResult(intent, REQUEST_CHOOSE_LOCK);
     }
 
     private void startChooseLockPinActivity() {
-        Intent intent = new Intent(getContext(), ChooseLockPasswordActivity.class);
-        intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
-                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+        Intent intent = new Intent(getContext(), ChooseLockPinActivity.class);
         startActivityForResult(intent, REQUEST_CHOOSE_LOCK);
     }
 }
diff --git a/tests/robotests/src/com/android/car/settings/security/ChooseLockPasswordActivityTest.java b/tests/robotests/src/com/android/car/settings/security/ChooseLockPasswordActivityTest.java
index e8dea99..464b0fc 100644
--- a/tests/robotests/src/com/android/car/settings/security/ChooseLockPasswordActivityTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/ChooseLockPasswordActivityTest.java
@@ -16,14 +16,9 @@
 
 package com.android.car.settings.security;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -57,10 +52,9 @@
      */
     @Test
     public void testValidatePasswordTooShort() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
         String password = "lov";
         assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.DO_NOT_MATCH_PATTERN);
+                .isEqualTo(ChooseLockPasswordActivity.DOES_NOT_MATCH_PATTERN);
     }
 
     /**
@@ -69,10 +63,9 @@
      */
     @Test
     public void testValidatePasswordTooLong() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
         String password = "passwordtoolong";
         assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.DO_NOT_MATCH_PATTERN);
+                .isEqualTo(ChooseLockPasswordActivity.DOES_NOT_MATCH_PATTERN);
     }
 
     /**
@@ -81,10 +74,9 @@
      */
     @Test
     public void testValidatePasswordWhiteSpace() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
         String password = "pass wd";
         assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.DO_NOT_MATCH_PATTERN);
+                .isEqualTo(ChooseLockPasswordActivity.DOES_NOT_MATCH_PATTERN);
     }
 
     /**
@@ -93,10 +85,9 @@
      */
     @Test
     public void testValidatePasswordNoDigit() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
         String password = "password";
         assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.DO_NOT_MATCH_PATTERN);
+                .isEqualTo(ChooseLockPasswordActivity.DOES_NOT_MATCH_PATTERN);
     }
 
     /**
@@ -105,10 +96,9 @@
      */
     @Test
     public void testValidatePasswordNonAscii() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
         String password = "1passwýd";
         assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.CONTAIN_INVALID_CHARACTERS);
+                .isEqualTo(ChooseLockPasswordActivity.CONTAINS_INVALID_CHARACTERS);
     }
 
     /**
@@ -117,34 +107,9 @@
      */
     @Test
     public void testValidatePasswordNoLetter() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_ALPHANUMERIC);
         String password = "123456";
         assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.DO_NOT_MATCH_PATTERN);
-    }
-
-    /**
-     * A test to check validatePassword works as expected for numeric complex password
-     * that has sequential digits.
-     */
-    @Test
-    public void testValidatePasswordSequentialDigits() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
-        String password = "1234";
-        assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.CONTAIN_SEQUENTIAL_DIGITS);
-    }
-
-    /**
-     * A test to check validatePassword works as expected for numeric password
-     * that contains non digits.
-     */
-    @Test
-    public void testValidatePasswordNoneDigits() {
-        mActivity.setPasswordQuality(PASSWORD_QUALITY_NUMERIC);
-        String password = "1a34";
-        assertThat(mActivity.validatePassword(password))
-                .isEqualTo(ChooseLockPasswordActivity.CONTAIN_NON_DIGITS);
+                .isEqualTo(ChooseLockPasswordActivity.DOES_NOT_MATCH_PATTERN);
     }
 
     /**
diff --git a/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternActivityTest.java b/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternActivityTest.java
index cce0e64..bff460b 100644
--- a/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternActivityTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/ChooseLockPatternActivityTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 Google Inc.
+ * Copyright (C) 2018 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.
@@ -16,10 +16,7 @@
 
 package com.android.car.settings.security;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
diff --git a/tests/robotests/src/com/android/car/settings/security/ChooseLockPinActivityTest.java b/tests/robotests/src/com/android/car/settings/security/ChooseLockPinActivityTest.java
new file mode 100644
index 0000000..c535525
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/ChooseLockPinActivityTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Intent;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+
+/**
+ * Tests for ChooseLockPinActivity class.
+ */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class ChooseLockPinActivityTest {
+    private ChooseLockPinActivity mActivity;
+
+    @Before
+    public void setUpPinActivity() {
+        mActivity = Robolectric.buildActivity(ChooseLockPinActivity.class)
+                .create()
+                .get();
+    }
+
+    /**
+     * A test to check validatePassword works as expected for pin that contains non digits.
+     */
+    @Test
+    public void testValidatePinContainingNonDigits() {
+        String password = "1a34";
+        assertThat(mActivity.validatePassword(password))
+                .isEqualTo(ChooseLockPinActivity.CONTAINS_NON_DIGITS);
+    }
+
+    /**
+     * A test to check validatePassword works as expected for pin with too few digits
+     */
+    @Test
+    public void testValidatePinWithTooFewDigits() {
+        String password = "12";
+        assertThat(mActivity.validatePassword(password))
+                .isEqualTo(ChooseLockPinActivity.TOO_FEW_DIGITS);
+    }
+
+    /**
+     * A test to check validatePassword works as expected for numeric complex password
+     * that has sequential digits.
+     */
+    @Test
+    public void testValidatePinWithSequentialDigits() {
+        ChooseLockPinActivity spyActivity = spy(mActivity);
+        doReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX)
+                .when(spyActivity)
+                .getPasswordQuality();
+
+        String password = "1234";
+        assertThat(spyActivity.validatePassword(password))
+                .isEqualTo(ChooseLockPinActivity.CONTAINS_SEQUENTIAL_DIGITS);
+    }
+
+    /**
+     * A test to verify that the activity is finished when save worker succeeds
+     */
+    @Test
+    public void testActivityIsFinishedWhenSaveWorkerSucceeds() {
+        ChooseLockPinActivity spyActivity = spy(mActivity);
+
+        Intent intent = new Intent();
+        intent.putExtra(SaveChosenLockWorkerBase.EXTRA_KEY_SUCCESS, true);
+        spyActivity.onChosenLockSaveFinished(intent);
+
+        verify(spyActivity).finish();
+    }
+
+    /**
+     * A test to verify that the UI stage is updated when save worker fails
+     */
+    @Test
+    public void testStageIsUpdatedWhenSaveWorkerFails() {
+        ChooseLockPinActivity spyActivity = spy(mActivity);
+        doNothing().when(spyActivity).updateStage(ChooseLockPinActivity.Stage.SaveFailure);
+
+        Intent intent = new Intent();
+        intent.putExtra(SaveChosenLockWorkerBase.EXTRA_KEY_SUCCESS, false);
+        spyActivity.onChosenLockSaveFinished(intent);
+
+        verify(spyActivity, never()).finish();
+        verify(spyActivity).updateStage(ChooseLockPinActivity.Stage.SaveFailure);
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/SaveLockPasswordWorkerTest.java b/tests/robotests/src/com/android/car/settings/security/SaveLockPasswordWorkerTest.java
index 94c5329..33d6765 100644
--- a/tests/robotests/src/com/android/car/settings/security/SaveLockPasswordWorkerTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/SaveLockPasswordWorkerTest.java
@@ -1,18 +1,18 @@
 /*
-* Copyright (C) 2018 Google Inc.
-*
-* 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.
-*/
+ * Copyright (C) 2018 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.car.settings.security;
 
diff --git a/tests/robotests/src/com/android/car/settings/security/SaveLockPatternWorkerTest.java b/tests/robotests/src/com/android/car/settings/security/SaveLockPatternWorkerTest.java
index 11de1dd..90d90d0 100644
--- a/tests/robotests/src/com/android/car/settings/security/SaveLockPatternWorkerTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/SaveLockPatternWorkerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 Google Inc.
+ * Copyright (C) 2018 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.