Add fingerprint settings.

- Improved layout.
- Enrollment is now working.
- Added vibration and progress feedback.
- Better fingerprint animation logic.
- Poke userActivity() when sensor is touched.
- Added progress animation.
- Only show fingerprint menu item on devices that have fingerprint hw
- Set View state to GONE for views that aren't shown & fix resulting layout issue
- Fix bug where stage wasn't advancing when returning from ChooseLockGeneric.
- Renamed FingerprintSettings to FingerprintEnroll
- Fixed bug with storing fingerprint ids that prevented the last one from being removed.
- Added better progress indication.  When remaining is at max, count that as the first step.
- Fix whitespace formatting in CL

Fixes bug 1953439

Change-Id: I721bf440c63640203af94ce21340d8281076c249
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4fa4383..c582e6c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -72,6 +72,8 @@
     <uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
     <uses-permission android:name="android.permission.OEM_UNLOCK_STATE" />
     <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
+    <uses-permission android:name="android.permission.MANAGE_FINGERPRINT" />
 
     <application android:label="@string/settings_label"
             android:icon="@mipmap/ic_launcher_settings"
@@ -1219,6 +1221,9 @@
         <activity android:name="ConfirmLockPassword"
             android:windowSoftInputMode="stateVisible|adjustResize"/>
 
+        <activity android:name="FingerprintSettings" android:exported="false"/>
+        <activity android:name="FingerprintEnroll" android:exported="false"/>
+
         <!-- Note this must not be exported since it returns the password in the intent -->
         <activity android:name="ConfirmLockPattern$InternalActivity"
             android:exported="false"/>
diff --git a/res/drawable-nodpi/fingerprint_anim00.png b/res/drawable-nodpi/fingerprint_anim00.png
new file mode 100644
index 0000000..97d2644
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim00.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim01.png b/res/drawable-nodpi/fingerprint_anim01.png
new file mode 100644
index 0000000..d6c86b8
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim01.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim02.png b/res/drawable-nodpi/fingerprint_anim02.png
new file mode 100644
index 0000000..ddc8ac3
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim02.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim03.png b/res/drawable-nodpi/fingerprint_anim03.png
new file mode 100644
index 0000000..3435c46
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim03.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim04.png b/res/drawable-nodpi/fingerprint_anim04.png
new file mode 100644
index 0000000..2f8f949
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim04.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim05.png b/res/drawable-nodpi/fingerprint_anim05.png
new file mode 100644
index 0000000..47178b7
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim05.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim06.png b/res/drawable-nodpi/fingerprint_anim06.png
new file mode 100644
index 0000000..e0a29d1
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim06.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim07.png b/res/drawable-nodpi/fingerprint_anim07.png
new file mode 100644
index 0000000..d92d75b
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim07.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim08.png b/res/drawable-nodpi/fingerprint_anim08.png
new file mode 100644
index 0000000..9c77868
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim08.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim09.png b/res/drawable-nodpi/fingerprint_anim09.png
new file mode 100644
index 0000000..5ebaa0c
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim09.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim10.png b/res/drawable-nodpi/fingerprint_anim10.png
new file mode 100644
index 0000000..b1f9ca9
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim10.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim11.png b/res/drawable-nodpi/fingerprint_anim11.png
new file mode 100644
index 0000000..b8fb5d7
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim11.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_anim12.png b/res/drawable-nodpi/fingerprint_anim12.png
new file mode 100644
index 0000000..97d2644
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_anim12.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_in_app_indicator.png b/res/drawable-nodpi/fingerprint_in_app_indicator.png
new file mode 100644
index 0000000..4af83e6
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_in_app_indicator.png
Binary files differ
diff --git a/res/drawable-nodpi/fingerprint_sensor_location.png b/res/drawable-nodpi/fingerprint_sensor_location.png
new file mode 100644
index 0000000..0486dba
--- /dev/null
+++ b/res/drawable-nodpi/fingerprint_sensor_location.png
Binary files differ
diff --git a/res/drawable/fingerprint_animation.xml b/res/drawable/fingerprint_animation.xml
new file mode 100644
index 0000000..a2c1030
--- /dev/null
+++ b/res/drawable/fingerprint_animation.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 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.
+-->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/fingerprint_animation"
+        android:oneshot="false"
+        android:duration="1000">
+    <item android:drawable="@drawable/fingerprint_anim00" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim01" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim02" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim03" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim04" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim05" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim06" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim07" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim08" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim09" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim10" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim11" android:duration="77" />
+    <item android:drawable="@drawable/fingerprint_anim12" android:duration="77" />
+</animation-list>
diff --git a/res/drawable/fingerprint_progress_ring.xml b/res/drawable/fingerprint_progress_ring.xml
new file mode 100644
index 0000000..a2d9cba
--- /dev/null
+++ b/res/drawable/fingerprint_progress_ring.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape
+            android:innerRadius="96dip"
+            android:shape="ring"
+            android:thickness="4dip">
+            <gradient
+                android:startColor="@color/fingerprint_progress_ring_color"
+                android:endColor="@color/fingerprint_progress_ring_color"
+                android:angle="180"
+            />
+        </shape>
+    </item>
+</layer-list>
diff --git a/res/drawable/fingerprint_progress_ring_bg.xml b/res/drawable/fingerprint_progress_ring_bg.xml
new file mode 100644
index 0000000..52d439b
--- /dev/null
+++ b/res/drawable/fingerprint_progress_ring_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:innerRadius="96dip"
+    android:shape="ring"
+    android:thickness="4dip"
+    android:color="@color/fingerprint_progress_ring_bg">
+</shape>
diff --git a/res/layout-land/fingerprint_enroll.xml b/res/layout-land/fingerprint_enroll.xml
new file mode 100644
index 0000000..8954d74
--- /dev/null
+++ b/res/layout-land/fingerprint_enroll.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_vertical">
+
+    <!-- Left area -->
+    <LinearLayout
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="0dip"
+            android:layout_weight="0.4"
+            android:background="@color/fingerprint_title_area_bg"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/fingerprint_enroll_title"
+                android:background="@color/fingerprint_title_area_bg"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="20dip"
+                android:layout_marginStart="40dip"
+                android:layout_marginEnd="40dip"
+                style="@style/TextAppearance.FingerprintTitle"
+                android:layout_alignParentBottom="true"/>
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/fingerprint_enroll_message"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="0.6"
+            android:layout_marginTop="24dip"
+            android:layout_marginStart="40dip"
+            android:layout_marginEnd="40dip"
+            android:layout_marginBottom="36dip"
+            style="@style/TextAppearance.FingerprintMessage"/>
+
+    </LinearLayout>
+
+    <!-- Right area -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/fingerprint_view_selector"
+            android:layout_width="wrap_content"
+            android:layout_height="0dip"
+            android:orientation="vertical"
+            android:layout_weight="1"
+            android:layout_gravity="center">
+
+            <FrameLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:background="@drawable/fingerprint_progress_ring_bg">
+
+                <!-- One of the following views will show for any given mode -->
+                <ProgressBar
+                    android:id="@+id/fingerprint_progress_bar"
+                    style="?android:attr/progressBarStyleHorizontal"
+                    android:layout_width="200dip"
+                    android:layout_height="200dip"
+                    android:max="100"
+                    android:progress="0"
+                    android:indeterminate="false"
+                    android:progressDrawable="@drawable/fingerprint_progress_ring" />
+
+                <ImageView
+                    android:id="@+id/fingerprint_sensor_location"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:src="@drawable/fingerprint_sensor_location"
+                    android:visibility="gone"/>
+
+                <ImageView
+                    android:id="@+id/fingerprint_animator"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center"
+                    android:src="@drawable/fingerprint_animation"
+                    android:visibility="visible" />
+
+                <ImageView
+                    android:id="@+id/fingerprint_in_app_indicator"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:src="@drawable/fingerprint_in_app_indicator"
+                    android:visibility="visible" />
+            </FrameLayout>
+
+        </LinearLayout>
+
+        <!-- Button area -->
+        <LinearLayout
+            android:id="@+id/fingerprint_enroll_button_area"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <Button
+                android:id="@+id/fingerprint_enroll_button_add"
+                style="@style/SecurityPreferenceButton"
+                android:text="@string/fingerprint_enroll_button_add" />
+
+            <Button
+                android:id="@+id/fingerprint_enroll_button_next"
+                style="@style/SecurityPreferenceButton"
+                android:text="@string/fingerprint_enroll_button_next" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/fingerprint_enroll.xml b/res/layout/fingerprint_enroll.xml
new file mode 100644
index 0000000..d7bf046
--- /dev/null
+++ b/res/layout/fingerprint_enroll.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal">
+
+    <!-- Upper title area -->
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="0.3"
+        android:background="@color/fingerprint_title_area_bg">
+
+        <TextView
+            android:id="@+id/fingerprint_enroll_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="20dip"
+            android:layout_marginStart="40dip"
+            android:layout_marginEnd="40dip"
+            style="@style/TextAppearance.FingerprintTitle"
+            android:layout_alignParentBottom="true"/>
+
+    </RelativeLayout>
+
+    <!-- Lower message area -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="0.6"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/fingerprint_enroll_message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dip"
+            android:layout_marginStart="40dip"
+            android:layout_marginEnd="40dip"
+            android:layout_marginBottom="36dip"
+            style="@style/TextAppearance.FingerprintMessage"/>
+
+        <FrameLayout
+            android:id="@+id/fingerprint_view_selector"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:layout_gravity="center">
+
+            <FrameLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:background="@drawable/fingerprint_progress_ring_bg">
+
+                <!-- One of the following views will show for any given mode -->
+                <ProgressBar
+                    android:id="@+id/fingerprint_progress_bar"
+                    style="?android:attr/progressBarStyleHorizontal"
+                    android:layout_width="200dip"
+                    android:layout_height="200dip"
+                    android:max="100"
+                    android:progress="0"
+                    android:indeterminate="false"
+                    android:progressDrawable="@drawable/fingerprint_progress_ring" />
+
+                <ImageView
+                    android:id="@+id/fingerprint_sensor_location"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:src="@drawable/fingerprint_sensor_location" />
+
+                <ImageView
+                    android:id="@+id/fingerprint_animator"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center"
+                    android:src="@drawable/fingerprint_animation" />
+
+                <ImageView
+                    android:id="@+id/fingerprint_in_app_indicator"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:src="@drawable/fingerprint_in_app_indicator" />
+            </FrameLayout>
+
+        </FrameLayout>
+
+    </LinearLayout>
+
+    <!-- Button area -->
+    <LinearLayout
+        android:id="@+id/fingerprint_enroll_button_area"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <Button
+            android:id="@+id/fingerprint_enroll_button_add"
+            style="@style/SecurityPreferenceButton"
+            android:text="@string/fingerprint_enroll_button_add" />
+
+        <Button
+            android:id="@+id/fingerprint_enroll_button_next"
+            style="@style/SecurityPreferenceButton"
+            android:text="@string/fingerprint_enroll_button_next" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6f97f40..21b5924 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -60,6 +60,12 @@
     <color name="unlock_pattern_view_success_color">@color/theme_accent</color>
     <color name="unlock_pattern_view_error_color">#fff4511e</color>
 
+    <color name="fingerprint_title_area_bg">#ff009688</color>
+    <color name="fingerprint_title_color">#ffffffff</color>
+    <color name="fingerprint_message_color">#de000000</color>
+    <color name="fingerprint_progress_ring_color">#ff009688</color>
+    <color name="fingerprint_progress_ring_bg">#20000000</color>
+
     <color name="running_processes_system_ram">#ff384248</color>
     <color name="running_processes_apps_ram">#ff009587</color>
     <color name="running_processes_free_ram">#ffced7db</color>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e560c1..0e535c6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -756,6 +756,54 @@
     <!-- In the security screen, the header title for settings related to  Passwords-->
     <string name="security_passwords_title">Passwords</string>
 
+    <!-- Fingerprint enrollment and settings --><skip />
+    <!-- Title shown for menu item that launches fingerprint settings or enrollment [CHAR LIMIT=22] -->
+    <string name="security_settings_fingerprint_preference_title">Fingerprint</string>
+    <!-- Message shown for menu item that launches fingerprint settings or enrollment -->
+    <plurals name="security_settings_fingerprint_preference_summary">
+        <item quantity="one"><xliff:g id="count">%1$d</xliff:g> fingerprint enrolled</item>
+        <item quantity="other"><xliff:g id="count">%1$d</xliff:g> fingerprints enrolled</item>
+    </plurals>
+    <!-- Introduction title shown in fingerprint enrollment dialog [CHAR LIMIT=22] -->
+    <string name="security_settings_fingerprint_enroll_onboard_title">Fingerprint setup</string>
+    <!-- Introduction detail message shown in fingerprint enrollment dialog -->
+    <string name="security_settings_fingerprint_enroll_onboard_message">
+        To use your fingerprint to unlock your screen or confirm purchases, we\'ll need to:
+        \n\u2713 Set up your background screen lock method
+        \n\u2713 Add your fingerprint</string>
+    <!-- Title shown in fingerprint enrollment dialog to begin enrollment [CHAR LIMIT=22]-->
+    <string name="security_settings_fingerprint_enroll_start_title">Let\'s start!</string>
+    <!-- Message shown in fingerprint enrollment dialog to begin enrollment -->
+    <string name="security_settings_fingerprint_enroll_start_message">Put your finger on the fingerprint sensor. Lift after you feel a vibration.</string>
+    <!-- Title shown in fingerprint enrollment dialog to repeat touching the fingerprint sensor [CHAR LIMIT=22] -->
+    <string name="security_settings_fingerprint_enroll_repeat_title">Great! Now repeat.</string>
+    <!-- Message shown in fingerprint enrollment dialog to repeat touching the fingerprint sensor -->
+    <string name="security_settings_fingerprint_enroll_repeat_message">Put the same finger on the fingerprint sensor and lift after you feel a vibration.</string>
+    <!-- Title shown in fingerprint enrollment dialog once enrollment is completed [CHAR LIMIT=22] -->
+    <string name="security_settings_fingerprint_enroll_finish_title">Fingerprint added!</string>
+    <!-- Message shown in fingerprint enrollment dialog once enrollment is completed -->
+    <string name="security_settings_fingerprint_enroll_finish_message">Whenever you see this icon, you can use your fingerprint for identification or to authorize a purchase.</string>
+    <!-- Button text shown at the end of enrollment that allows the user to add another fingerprint -->
+    <string name="fingerprint_enroll_button_add">Add</string>
+    <!-- Button text shown at the end of enrollment that allows the user to move to the next step -->
+    <string name="fingerprint_enroll_button_next">Next</string>
+    <!-- Error message shown when the fingerprint cannot be recognized -->
+    <string name="fingerprint_acquired_try_again">Partial fingerprint detected. Please try again.</string>
+    <!-- Error message shown when the fingerprint sensor needs cleaning -->
+    <string name="fingerprint_acquired_imager_dirty">Fingerprint sensor is dirty. Please clean and try again.</string>
+    <!-- Error message shown when the user removes their finger from the sensor too quickly -->
+    <string name="fingerprint_acquired_too_fast">Finger moved to fast. Please try again.</string>
+    <!-- Error message shown when the user moves their finger too slowly -->
+    <string name="fingerprint_acquired_too_slow">Finger moved to slow. Please try again.</string>
+    <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint -->
+    <string name="fingerprint_error_unable_to_process">Unable to process. Try again.</string>
+    <!-- Error message shown when the fingerprint hardware can't be accessed -->
+    <string name="fingerprint_error_hw_not_available">Hardware not available.</string>
+    <!-- Error message shown when the fingerprint hardware has run out of room for storing fingerprints -->
+    <string name="fingerprint_error_no_space">Fingerprint can\'t be stored. Please remove an existing fingerprint.</string>
+    <!-- Error message shown when the fingerprint hardware timer has expired and the user needs to restart the operation. -->
+    <string name="fingerprint_error_timeout">Fingerprint time out reached. Try again.</string>
+
     <!-- Title of the preferences category for preference items to control encryption -->
     <string name="crypt_keeper_settings_title">Encryption</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 82f447f..8ea359d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -351,4 +351,19 @@
 
     <style name="TextAppearance.ResultTitle" parent="TextAppearance.CategoryTitle">
     </style>
+
+    <style name="TextAppearance.FingerprintTitle"
+        parent="android:TextAppearance.Material.Large.Inverse">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">24sp</item>
+        <item name="android:textColor">@color/fingerprint_title_color</item>
+    </style>
+
+    <style name="TextAppearance.FingerprintMessage"
+        parent="android:TextAppearance.Material.Medium.Inverse">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">@color/fingerprint_message_color</item>
+    </style>
+
 </resources>
diff --git a/src/com/android/settings/FingerprintEnroll.java b/src/com/android/settings/FingerprintEnroll.java
new file mode 100644
index 0000000..24f479e
--- /dev/null
+++ b/src/com/android/settings/FingerprintEnroll.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2015 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;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.AnimationDrawable;
+import android.media.AudioAttributes;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.service.fingerprint.FingerprintManager;
+import android.service.fingerprint.FingerprintManagerReceiver;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * Wizard to enroll a fingerprint
+ */
+public class FingerprintEnroll extends SettingsActivity {
+    /**
+     * Used by the choose fingerprint wizard to indicate the wizard is
+     * finished, and each activity in the wizard should finish.
+     * <p>
+     * Previously, each activity in the wizard would finish itself after
+     * starting the next activity. However, this leads to broken 'Back'
+     * behavior. So, now an activity does not finish itself until it gets this
+     * result.
+     */
+    static final int RESULT_FINISHED = RESULT_FIRST_USER;
+
+    @Override
+    public Intent getIntent() {
+        Intent modIntent = new Intent(super.getIntent());
+        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintEnrollFragment.class.getName());
+        return modIntent;
+    }
+
+    @Override
+    protected boolean isValidFragment(String fragmentName) {
+        if (FingerprintEnrollFragment.class.getName().equals(fragmentName)) return true;
+        return false;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        super.onCreate(savedInstanceState);
+        CharSequence msg = getText(R.string.security_settings_fingerprint_preference_title);
+        setTitle(msg);
+    }
+
+    public static class FingerprintEnrollFragment extends Fragment implements View.OnClickListener {
+        private static final String TAG = "FingerprintEnroll";
+        private static final boolean DEBUG = true;
+        private static final int CONFIRM_REQUEST = 101;
+        private static final int CHOOSE_LOCK_GENERIC_REQUEST = 102;
+        private static final long ENROLL_TIMEOUT = 300*1000;
+
+        private PowerManager mPowerManager;
+        private FingerprintManager mFingerprintManager;
+        private View mContentView;
+        private TextView mTitleText;
+        private TextView mMessageText;
+        private Stage mStage;
+        private int mEnrollmentSteps;
+        private boolean mEnrolling;
+        private Vibrator mVibrator;
+        private ProgressBar mProgressBar;
+        private ImageView mFingerprintAnimator;
+        private ObjectAnimator mProgressAnim;
+        private final AnimatorListener mProgressAnimationListener = new AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) { }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) { }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mProgressBar.getProgress() >= 100) {
+                    updateStage(Stage.EnrollingFinish);
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) { }
+        };
+
+        // This contains a list of all views managed by the UI. Used to determine which views
+        // need to be shown/hidden at each stage. It should be the union of the lists that follow
+        private static final int MANAGED_VIEWS[] = {
+            R.id.fingerprint_sensor_location,
+            R.id.fingerprint_animator,
+            R.id.fingerprint_enroll_button_area,
+            R.id.fingerprint_in_app_indicator,
+            R.id.fingerprint_enroll_button_add,
+            R.id.fingerprint_enroll_button_next,
+            R.id.fingerprint_progress_bar
+        };
+
+        private static final int VIEWS_ENROLL_ONBOARD[] = {
+            R.id.fingerprint_enroll_button_area,
+            R.id.fingerprint_enroll_button_next
+        };
+
+        private static final int VIEWS_ENROLL_START[] = {
+            R.id.fingerprint_sensor_location,
+            R.id.fingerprint_progress_bar
+        };
+
+        private static final int VIEWS_ENROLL_PROGRESS[] = {
+            R.id.fingerprint_animator,
+            R.id.fingerprint_progress_bar
+        };
+
+        private static final int VIEWS_ENROLL_FINISH[] = {
+            R.id.fingerprint_enroll_button_area,
+            R.id.fingerprint_in_app_indicator,
+            R.id.fingerprint_enroll_button_add,
+            R.id.fingerprint_enroll_button_next
+        };
+
+        enum Stage {
+            EnrollingOnboarding(R.string.security_settings_fingerprint_enroll_onboard_title,
+                    R.string.security_settings_fingerprint_enroll_onboard_message,
+                    VIEWS_ENROLL_ONBOARD),
+            EnrollingStart(R.string.security_settings_fingerprint_enroll_start_title,
+                    R.string.security_settings_fingerprint_enroll_start_message,
+                    VIEWS_ENROLL_START),
+            EnrollingRepeat(R.string.security_settings_fingerprint_enroll_repeat_title,
+                    R.string.security_settings_fingerprint_enroll_repeat_message,
+                    VIEWS_ENROLL_PROGRESS),
+            EnrollingFinish(R.string.security_settings_fingerprint_enroll_finish_title,
+                    R.string.security_settings_fingerprint_enroll_finish_message,
+                    VIEWS_ENROLL_FINISH);
+
+            Stage(int title, int message, int[] enabledViewIds) {
+                this.title = title;
+                this.message = message;
+                this.enabledViewIds = enabledViewIds;
+            }
+
+            public int title;
+            public int message;
+            public int[] enabledViewIds;
+        };
+
+        void updateStage(Stage stage) {
+            if (DEBUG) Log.v(TAG, "updateStage(" + stage.toString() + ")");
+
+            // Show/hide views
+            for (int i = 0; i < MANAGED_VIEWS.length; i++) {
+                mContentView.findViewById(MANAGED_VIEWS[i]).setVisibility(View.INVISIBLE);
+            }
+            for (int i = 0; i < stage.enabledViewIds.length; i++) {
+                mContentView.findViewById(stage.enabledViewIds[i]).setVisibility(View.VISIBLE);
+            }
+
+            setTitleMessage(stage.title);
+            setMessage(stage.message);
+
+            if (mStage != stage) {
+                onStageChanged(stage);
+                mStage = stage;
+            }
+        }
+
+        private void startFingerprintAnimator() {
+            AnimationDrawable drawable = (AnimationDrawable) mFingerprintAnimator.getDrawable();
+            drawable.start();
+        }
+
+        private void stopFingerprintAnimator() {
+            AnimationDrawable drawable = (AnimationDrawable) mFingerprintAnimator.getDrawable();
+            drawable.stop();
+            drawable.setLevel(0);
+        }
+
+        private void onStageChanged(Stage stage) {
+            // Update state
+            switch (stage) {
+                case EnrollingOnboarding:
+                    mEnrollmentSteps = -1;
+                    mEnrolling = false;
+                    break;
+
+                case EnrollingStart:
+                    mEnrollmentSteps = -1;
+                    mFingerprintManager.startListening(mReceiver);
+                    mFingerprintManager.enroll(ENROLL_TIMEOUT);
+                    mProgressBar.setProgress(0);
+                    mEnrolling = true;
+                    startFingerprintAnimator(); // XXX hack - this should follow fingerprint detection
+                    break;
+
+                case EnrollingRepeat:
+                    break;
+
+                case EnrollingFinish:
+                    stopFingerprintAnimator(); // XXX hack - this should follow fingerprint detection
+                    mFingerprintManager.stopListening();
+                    mEnrolling = false;
+                    break;
+
+                default:
+                    mFingerprintManager.stopListening();
+                    break;
+            }
+        }
+
+        private void cancelEnrollment() {
+            if (mEnrolling) {
+                if (DEBUG) Log.v(TAG, "Cancel enrollment\n");
+                mFingerprintManager.enrollCancel();
+                mEnrolling = false;
+            }
+        }
+
+        @Override
+        public void onDetach() {
+            super.onDetach();
+            // Do a little cleanup
+            cancelEnrollment();
+            mFingerprintManager.stopListening();
+        }
+
+        private void updateProgress(int progress) {
+            if (DEBUG) Log.v(TAG, "Progress: " + progress);
+            if (mVibrator != null) {
+                mVibrator.vibrate(100, new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build());
+            }
+            if (mProgressAnim != null) {
+                mProgressAnim.cancel();
+            }
+            ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress",
+                    mProgressBar.getProgress(), progress);
+            anim.addListener(mProgressAnimationListener);
+            anim.start();
+            mProgressAnim = anim;
+        }
+
+        private void setMessage(int id) {
+            if (id != 0) mMessageText.setText(id);
+        }
+
+        private void setTitleMessage(int title) {
+            if (title != 0) mTitleText.setText(title);
+        }
+
+        private FingerprintManagerReceiver mReceiver = new FingerprintManagerReceiver() {
+            public void onEnrollResult(int fingerprintId, int remaining) {
+                if (DEBUG) Log.v(TAG, "onEnrollResult(id=" + fingerprintId + ", rem=" + remaining);
+                if (mEnrollmentSteps == -1) {
+                    mEnrollmentSteps = remaining;
+                    updateStage(Stage.EnrollingRepeat);
+                }
+                if (remaining >= 0) {
+                    int progress = Math.max(0, mEnrollmentSteps + 1 - remaining);
+                    updateProgress(100*progress / (mEnrollmentSteps + 1));
+                    // Treat fingerprint like a touch event
+                    mPowerManager.userActivity(SystemClock.uptimeMillis(),
+                            PowerManager.USER_ACTIVITY_EVENT_OTHER,
+                            PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS);
+                }
+            }
+
+            public void onError(int error) {
+                switch(error) {
+                    case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
+                        setMessage(R.string.fingerprint_error_unable_to_process);
+                        break;
+                    case FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE:
+                        setMessage(R.string.fingerprint_error_hw_not_available);
+                        break;
+                    case FingerprintManager.FINGERPRINT_ERROR_NO_SPACE:
+                        setMessage(R.string.fingerprint_error_no_space);
+                        break;
+                    case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT:
+                        setMessage(R.string.fingerprint_error_timeout);
+                        break;
+                    case FingerprintManager.FINGERPRINT_ERROR_NO_RECEIVER:
+                        Log.w(TAG, "Receiver not registered");
+                        break;
+                }
+            }
+
+            public void onRemoved(int fingerprintId) {
+                if (DEBUG) Log.v(TAG, "onRemoved(id=" + fingerprintId + ")");
+            }
+
+            @Override
+            public void onProcessed(int fingerprintId) {
+                if (DEBUG) Log.v(TAG, "onProcessed(id=" + fingerprintId + ")");
+            }
+
+            public void onAcquired(int scanInfo) {
+                int msgId = 0;
+                startFingerprintAnimator();
+                switch(scanInfo) {
+                    case FingerprintManager.FINGERPRINT_ACQUIRED_GOOD:
+                        break;
+                    case FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
+                        msgId = R.string.fingerprint_acquired_imager_dirty;
+                        break;
+                    case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_SLOW:
+                        msgId = R.string.fingerprint_acquired_too_fast;
+                        break;
+                    case FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST:
+                        msgId = R.string.fingerprint_acquired_too_slow;
+                        break;
+                    case FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL:
+                    case FingerprintManager.FINGERPRINT_ACQUIRED_INSUFFICIENT:
+                        msgId = R.string.fingerprint_acquired_try_again;
+                        break;
+                    default:
+                        // Try not to be too verbose in the UI. The user just needs to try again.
+                        // Log the message so we can dig into the issue if necessary.
+                        Log.w(TAG, "Try again because scanInfo was " + scanInfo);
+                        msgId = R.string.fingerprint_acquired_try_again;
+                        break;
+                }
+                setMessage(msgId);
+            }
+        };
+
+        private boolean runConfirmDeviceCredentials(int request) {
+            if (DEBUG) Log.v(TAG, "runKeyguardConfirmation(" + request + ")");
+            Resources res = getResources();
+            return new ChooseLockSettingsHelper(getActivity(), this)
+                    .launchConfirmationActivity(request,
+                            res.getText(R.string.master_clear_gesture_prompt),
+                            res.getText(R.string.master_clear_gesture_explanation));
+        }
+
+        @Override
+        public void onActivityResult(int requestCode, int resultCode, Intent data) {
+            super.onActivityResult(requestCode, resultCode, data);
+
+            if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST) {
+                if (resultCode == RESULT_FINISHED) {
+                    // The lock pin/pattern/password was set. Start enrolling!
+                    updateStage(Stage.EnrollingStart);
+                }
+            }
+        }
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            final Activity activity = getActivity();
+            mFingerprintManager = (FingerprintManager)activity
+                    .getSystemService(Context.FINGERPRINT_SERVICE);
+            mVibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
+            mPowerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
+
+            mContentView = inflater.inflate(R.layout.fingerprint_enroll, null);
+            mTitleText = (TextView) mContentView.findViewById(R.id.fingerprint_enroll_title);
+            mMessageText = (TextView) mContentView.findViewById(R.id.fingerprint_enroll_message);
+            mProgressBar = (ProgressBar) mContentView.findViewById(R.id.fingerprint_progress_bar);
+            mFingerprintAnimator = (ImageView) mContentView.findViewById(R.id.fingerprint_animator);
+
+            final int buttons[] = {
+                R.id.fingerprint_enroll_button_add,
+                R.id.fingerprint_enroll_button_next };
+            for (int i = 0; i < buttons.length; i++) {
+                mContentView.findViewById(buttons[i]).setOnClickListener(this);
+            }
+
+            LockPatternUtils utils = new LockPatternUtils(activity);
+            if (!utils.isSecure()) {
+                // Device doesn't have any security. Set that up first.
+                updateStage(Stage.EnrollingOnboarding);
+            } else {
+                updateStage(Stage.EnrollingStart);
+            }
+            return mContentView;
+        }
+
+        @Override
+        public void onClick(View v) {
+            switch(v.getId()) {
+                case R.id.fingerprint_enroll_button_add:
+                    updateStage(Stage.EnrollingStart);
+                    break;
+                case R.id.fingerprint_enroll_button_next:
+                    if (mStage == Stage.EnrollingOnboarding) {
+                        launchChooseLock();
+                    } else if (mStage == Stage.EnrollingFinish) {
+                        getActivity().finish();
+                    } else {
+                        Log.v(TAG, "No idea what to do next!");
+                    }
+                    break;
+            }
+        }
+
+        private void launchChooseLock() {
+            Intent intent = new Intent();
+            intent.setClassName("com.android.settings", ChooseLockGeneric.class.getName());
+            startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST);
+        }
+    }
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index e809bb5..95826e4 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -41,6 +41,8 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.security.KeyStore;
+import android.service.fingerprint.FingerprintManager;
+import android.service.fingerprint.FingerprintManager.FingerprintItem;
 import android.service.trust.TrustAgentService;
 import android.telephony.TelephonyManager;
 import android.telephony.SubscriptionManager;
@@ -220,32 +222,12 @@
             }
         }
 
-        // Trust Agent preferences
+        // Fingerprint and trust agents
         PreferenceGroup securityCategory = (PreferenceGroup)
                 root.findPreference(KEY_SECURITY_CATEGORY);
         if (securityCategory != null) {
-            final boolean hasSecurity = mLockPatternUtils.isSecure();
-            ArrayList<TrustAgentComponentInfo> agents =
-                    getActiveTrustAgents(getPackageManager(), mLockPatternUtils);
-            for (int i = 0; i < agents.size(); i++) {
-                final TrustAgentComponentInfo agent = agents.get(i);
-                Preference trustAgentPreference =
-                        new Preference(securityCategory.getContext());
-                trustAgentPreference.setKey(KEY_TRUST_AGENT);
-                trustAgentPreference.setTitle(agent.title);
-                trustAgentPreference.setSummary(agent.summary);
-                // Create intent for this preference.
-                Intent intent = new Intent();
-                intent.setComponent(agent.componentName);
-                intent.setAction(Intent.ACTION_MAIN);
-                trustAgentPreference.setIntent(intent);
-                // Add preference to the settings menu.
-                securityCategory.addPreference(trustAgentPreference);
-                if (!hasSecurity) {
-                    trustAgentPreference.setEnabled(false);
-                    trustAgentPreference.setSummary(R.string.disabled_because_no_backup_security);
-                }
-            }
+            maybeAddFingerprintPreference(securityCategory);
+            addTrustAgentSettings(securityCategory);
         }
 
         // lock after preference
@@ -347,6 +329,58 @@
         return root;
     }
 
+    private void maybeAddFingerprintPreference(PreferenceGroup securityCategory) {
+        FingerprintManager fpm = (FingerprintManager) getActivity().getSystemService(
+                Context.FINGERPRINT_SERVICE);
+        if (!fpm.isHardwareDetected()) {
+            Log.v(TAG, "No fingerprint hardware detected!!");
+            return;
+        }
+        Preference fingerprintPreference = new Preference(securityCategory.getContext());
+        fingerprintPreference.setKey(KEY_TRUST_AGENT);
+        fingerprintPreference.setTitle(R.string.security_settings_fingerprint_preference_title);
+        Intent intent = new Intent();
+        List<FingerprintItem> items = fpm.getEnrolledFingerprints();
+        int fingerprintCount = items.size();
+        if (fingerprintCount > 0) {
+            fingerprintPreference.setSummary(getResources().getQuantityString(
+                    R.plurals.security_settings_fingerprint_preference_summary,
+                    fingerprintCount, fingerprintCount));
+            // TODO: Launch fingerprintSettings instead...
+            intent.setClassName("com.android.settings", FingerprintEnroll.class.getName());
+        } else {
+            // No fingerprints registered, launch directly into fingerprint enrollment wizard
+            intent.setClassName("com.android.settings", FingerprintEnroll.class.getName());
+        }
+        fingerprintPreference.setIntent(intent);
+        securityCategory.addPreference(fingerprintPreference);
+    }
+
+    private void addTrustAgentSettings(PreferenceGroup securityCategory) {
+        final boolean hasSecurity = mLockPatternUtils.isSecure();
+        ArrayList<TrustAgentComponentInfo> agents =
+                getActiveTrustAgents(getPackageManager(), mLockPatternUtils);
+        for (int i = 0; i < agents.size(); i++) {
+            final TrustAgentComponentInfo agent = agents.get(i);
+            Preference trustAgentPreference =
+                    new Preference(securityCategory.getContext());
+            trustAgentPreference.setKey(KEY_TRUST_AGENT);
+            trustAgentPreference.setTitle(agent.title);
+            trustAgentPreference.setSummary(agent.summary);
+            // Create intent for this preference.
+            Intent intent = new Intent();
+            intent.setComponent(agent.componentName);
+            intent.setAction(Intent.ACTION_MAIN);
+            trustAgentPreference.setIntent(intent);
+            // Add preference to the settings menu.
+            securityCategory.addPreference(trustAgentPreference);
+            if (!hasSecurity) {
+                trustAgentPreference.setEnabled(false);
+                trustAgentPreference.setSummary(R.string.disabled_because_no_backup_security);
+            }
+        }
+    }
+
     /* Return true if a there is a Slot that has Icc.
      */
     private boolean isSimIccReady() {