Merge "NEW Use Mockito 4.6.1 API" into tm-qpr-dev
diff --git a/res/layout-land/choose_lock_pattern_common.xml b/res/layout-land/choose_lock_pattern_common.xml
new file mode 100644
index 0000000..1673413
--- /dev/null
+++ b/res/layout-land/choose_lock_pattern_common.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 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.
+-->
+
+<!-- Used in phone portrait and tablet, as referenced in alias.xml. -->
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/setup_wizard_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:icon="@drawable/ic_lock">
+
+    <!-- takes up all space above button bar at bottom -->
+    <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+        android:id="@+id/topLayout"
+        style="@style/SudContentFrame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="vertical"
+        android:paddingLeft="0dp"
+        android:paddingRight="0dp">
+
+        <TextView
+            android:id="@+id/headerText"
+            style="@style/SudDescription.Glif"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:minLines="2"
+            android:gravity="center"
+            android:paddingStart="?attr/sudMarginStart"
+            android:paddingEnd="?attr/sudMarginEnd"
+            android:fontFamily="@*android:string/config_headlineFontFamily" />
+
+        <com.google.android.setupdesign.view.FillContentLayout
+            style="@style/LockPatternContainerStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:layout_weight="1">
+
+            <com.android.internal.widget.LockPatternView
+                android:id="@+id/lockPattern"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"/>
+
+        </com.google.android.setupdesign.view.FillContentLayout>
+
+        <TextView android:id="@+id/footerText"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center_horizontal"
+                  android:minHeight="24dp"
+                  android:textSize="14sp"
+                  android:visibility="gone"/>
+
+        <Button
+            android:id="@+id/screen_lock_options"
+            style="@style/SudGlifButton.Tertiary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/setup_lock_settings_options_button_label"
+            android:visibility="gone"/>
+    </com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
+
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout-land/confirm_lock_pattern_base.xml b/res/layout-land/confirm_lock_pattern_base.xml
new file mode 100644
index 0000000..4ae5de3
--- /dev/null
+++ b/res/layout-land/confirm_lock_pattern_base.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/setup_wizard_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:icon="@drawable/ic_enterprise">
+
+    <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+        android:id="@+id/topLayout"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingBottom="24dp">
+
+            <Button
+                android:id="@+id/cancelButton"
+                style="@style/SudGlifButton.Secondary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="?attr/sudMarginStart"
+                android:layout_marginEnd="?attr/sudMarginEnd"
+                android:layout_marginBottom="80dp"
+                android:text="@string/cancel" />
+
+            <Button
+                android:id="@+id/forgotButton"
+                style="@style/SudGlifButton.Secondary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="?attr/sudMarginStart"
+                android:layout_marginEnd="?attr/sudMarginEnd"
+                android:layout_gravity="center"
+                android:visibility="gone" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            style="@style/SudContentFrame"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="center"
+            android:paddingLeft="0dp"
+            android:paddingRight="0dp">
+
+            <com.google.android.setupdesign.view.FillContentLayout
+                style="@style/LockPatternContainerStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:layout_weight="1">
+
+                <com.android.internal.widget.LockPatternView
+                    android:id="@+id/lockPattern"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center" />
+
+            </com.google.android.setupdesign.view.FillContentLayout>
+
+            <TextView
+                style="@style/TextAppearance.ErrorText"
+                android:accessibilityLiveRegion="polite"
+                android:id="@+id/errorText"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:paddingStart="?attr/sudMarginStart"
+                android:paddingEnd="?attr/sudMarginEnd"
+                android:layout_marginTop="12dp"
+                android:gravity="center_vertical"/>
+
+        </LinearLayout>
+
+    </com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout-land/confirm_lock_pattern_normal_base.xml b/res/layout-land/confirm_lock_pattern_normal_base.xml
new file mode 100644
index 0000000..1e26912
--- /dev/null
+++ b/res/layout-land/confirm_lock_pattern_normal_base.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/setup_wizard_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:icon="@drawable/ic_lock">
+
+    <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
+        android:id="@+id/topLayout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <LinearLayout
+            style="@style/SudContentFrame"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="center"
+            android:paddingLeft="0dp"
+            android:paddingRight="0dp">
+
+            <com.google.android.setupdesign.view.FillContentLayout
+                style="@style/LockPatternContainerStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:layout_weight="1">
+
+                <com.android.internal.widget.LockPatternView
+                    android:id="@+id/lockPattern"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center" />
+
+            </com.google.android.setupdesign.view.FillContentLayout>
+
+            <TextView
+                style="@style/TextAppearance.ErrorText"
+                android:accessibilityLiveRegion="polite"
+                android:id="@+id/errorText"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginStart="?attr/sudMarginStart"
+                android:layout_marginEnd="?attr/sudMarginEnd"
+                android:gravity="center_vertical"/>
+
+            <Button
+                android:id="@+id/cancelButton"
+                style="@style/SudGlifButton.Secondary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="?attr/sudMarginStart"
+                android:layout_marginEnd="?attr/sudMarginEnd"
+                android:layout_marginBottom="80dp"
+                android:text="@string/cancel" />
+
+        </LinearLayout>
+
+    </com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient>
+
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout/choose_lock_pattern_common.xml b/res/layout/choose_lock_pattern_common.xml
index 2dd8cd5..b63b2c6 100644
--- a/res/layout/choose_lock_pattern_common.xml
+++ b/res/layout/choose_lock_pattern_common.xml
@@ -51,13 +51,12 @@
             style="@style/LockPatternContainerStyle"
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            android:minHeight="@dimen/choose_lockscreen_min_height"
             android:layout_weight="1">
 
             <com.android.internal.widget.LockPatternView
                 android:id="@+id/lockPattern"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
+                android:layout_width="@dimen/biometric_auth_pattern_view_size"
+                android:layout_height="@dimen/biometric_auth_pattern_view_size"
                 android:layout_gravity="center"/>
 
         </com.google.android.setupdesign.view.FillContentLayout>
@@ -66,7 +65,7 @@
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:layout_gravity="center_horizontal"
-                  android:minHeight="50dip"
+                  android:minHeight="24dp"
                   android:textSize="14sp"
                   android:visibility="gone"/>
 
diff --git a/res/layout/confirm_lock_pattern_base.xml b/res/layout/confirm_lock_pattern_base.xml
index 15c6121..5b19105 100644
--- a/res/layout/confirm_lock_pattern_base.xml
+++ b/res/layout/confirm_lock_pattern_base.xml
@@ -67,13 +67,12 @@
                 style="@style/LockPatternContainerStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="0dp"
-                android:minHeight="@dimen/choose_lockscreen_min_height"
                 android:layout_weight="1">
 
                 <com.android.internal.widget.LockPatternView
                     android:id="@+id/lockPattern"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
+                    android:layout_width="@dimen/biometric_auth_pattern_view_size"
+                    android:layout_height="@dimen/biometric_auth_pattern_view_size"
                     android:layout_gravity="center" />
 
             </com.google.android.setupdesign.view.FillContentLayout>
diff --git a/res/layout/confirm_lock_pattern_normal_base.xml b/res/layout/confirm_lock_pattern_normal_base.xml
index 7fd6172..5d1ca7c 100644
--- a/res/layout/confirm_lock_pattern_normal_base.xml
+++ b/res/layout/confirm_lock_pattern_normal_base.xml
@@ -39,13 +39,12 @@
                 style="@style/LockPatternContainerStyle"
                 android:layout_width="wrap_content"
                 android:layout_height="0dp"
-                android:minHeight="@dimen/choose_lockscreen_min_height"
                 android:layout_weight="1">
 
                 <com.android.internal.widget.LockPatternView
                     android:id="@+id/lockPattern"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
+                    android:layout_width="@dimen/biometric_auth_pattern_view_size"
+                    android:layout_height="@dimen/biometric_auth_pattern_view_size"
                     android:layout_gravity="center" />
 
             </com.google.android.setupdesign.view.FillContentLayout>
diff --git a/res/raw/face_posture_guidance_lottie.json b/res/raw/face_posture_guidance_lottie.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/raw/face_posture_guidance_lottie.json
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 5248da6..3942430 100755
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -28,4 +28,8 @@
     <!-- Display, Screen zoom -->
     <dimen name="screen_zoom_preview_height">160dp</dimen>
 
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
 </resources>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
new file mode 100644
index 0000000..e6fb332
--- /dev/null
+++ b/res/values-land/styles.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<resources>
+    <style name="LockPatternContainerStyle">
+        <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingBottom">0dp</item>
+        <item name="android:paddingHorizontal">0dp</item>
+        <item name="android:paddingTop">0dp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/res/values-sw360dp/dimens.xml b/res/values-sw360dp/dimens.xml
index 23a1176..07a7bb7 100755
--- a/res/values-sw360dp/dimens.xml
+++ b/res/values-sw360dp/dimens.xml
@@ -25,4 +25,7 @@
     <!-- Suggestion cards-->
     <dimen name="suggestion_card_padding_bottom_one_card">22dp</dimen>
 
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
+
 </resources>
diff --git a/res/values-sw392dp-land/dimens.xml b/res/values-sw392dp-land/dimens.xml
new file mode 100755
index 0000000..920a0ec
--- /dev/null
+++ b/res/values-sw392dp-land/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<resources>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">248dp</dimen>
+
+</resources>
diff --git a/res/values-sw392dp/dimens.xml b/res/values-sw392dp/dimens.xml
new file mode 100755
index 0000000..f90d4ee
--- /dev/null
+++ b/res/values-sw392dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<resources>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
+
+</resources>
diff --git a/res/values-sw410dp-land/dimens.xml b/res/values-sw410dp-land/dimens.xml
new file mode 100755
index 0000000..644486d
--- /dev/null
+++ b/res/values-sw410dp-land/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<resources>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">248dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">348dp</dimen>
+
+</resources>
diff --git a/res/values-sw410dp/dimens.xml b/res/values-sw410dp/dimens.xml
new file mode 100755
index 0000000..5120e4a
--- /dev/null
+++ b/res/values-sw410dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<resources>
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+
+</resources>
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
index 015a6af..2016650 100755
--- a/res/values-sw600dp-land/dimens.xml
+++ b/res/values-sw600dp-land/dimens.xml
@@ -25,4 +25,8 @@
     <!-- Padding for screen pinning -->
     <dimen name="screen_pinning_padding_start">128dp</dimen>
     <dimen name="screen_pinning_padding_end">128dp</dimen>
+
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">298dp</dimen>
+
 </resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2e821e0..02ff494 100755
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -55,4 +55,7 @@
     <dimen name="screen_pinning_padding_end">40dp</dimen>
 
     <dimen name="settings_panel_width">560dp</dimen>
+
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index c701044..6f5bddf 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -38,4 +38,8 @@
     <dimen name="wifi_assistant_padding_start_end">24dp</dimen>
     <dimen name="wifi_assistant_padding">25dp</dimen>
     <dimen name="wifi_assistant_text_padding">24dp</dimen>
+
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+
 </resources>
diff --git a/res/values-sw800dp/dimens.xml b/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..48392ef
--- /dev/null
+++ b/res/values-sw800dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<resources>
+
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 4aa4142..f49eff7 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -283,6 +283,19 @@
     <!-- ComponentName to launch a vendor-specific enrollment activity if available -->
     <string name="config_face_enroll" translatable="false"></string>
 
+    <!-- ComponentName to launch a vendor-specific posture guidance activity if available -->
+    <string name="config_face_enroll_guidance_page" translatable="false"></string>
+
+    <!-- Whether to support posture listening for face auth, default is 0(DEVICE_POSTURE_UNKNOWN)
+     means setting will try listening on device posture changes.
+     0 : DEVICE_POSTURE_UNKNOWN
+     1 : DEVICE_POSTURE_CLOSED
+     2 : DEVICE_POSTURE_HALF_OPENED
+     3 : DEVICE_POSTURE_OPENED
+     4 : DEVICE_POSTURE_FLIPPED
+     -->
+    <integer name="config_face_enroll_supported_posture">0</integer>
+
     <!-- Whether to show the "less secure" info section on the face enroll intro screen -->
     <bool name="config_face_intro_show_less_secure">false</bool>
 
@@ -292,6 +305,9 @@
     <!-- Whether to use the Lottie animation for the face education enrollment screen -->
     <bool name="config_face_education_use_lottie">false</bool>
 
+    <!-- Whether to support enrollment during setup wizard flow -->
+    <bool name="config_suw_support_face_enroll">true</bool>
+
     <!-- App intent -->
     <string name="config_account_intent_uri" translatable="false"></string>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index afb6f68..84cfe63 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -242,6 +242,10 @@
     <item name="face_preview_scale" format="float" type="dimen">1.0</item>
     <dimen name="face_enroll_intro_illustration_margin_bottom">0dp</dimen>
 
+    <!-- Lock pattern view size, align sysui biometric_auth_pattern_view_size -->
+    <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+    <dimen name="biometric_auth_pattern_view_max_size">@dimen/biometric_auth_pattern_view_size</dimen>
+
     <!-- Confirm device credentials -->
     <dimen name="confirm_credentials_security_method_margin">48dp</dimen>
     <dimen name="confirm_credentials_layout_width">@dimen/match_parent</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 4fc6e9f..ce683724 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -432,10 +432,11 @@
     </style>
 
     <style name="LockPatternContainerStyle">
-        <item name="android:maxHeight">620dp</item>
-        <item name="android:maxWidth">620dp</item>
-        <item name="android:minHeight">0dp</item>
-        <item name="android:minWidth">0dp</item>
+        <item name="android:gravity">center</item>
+        <item name="android:maxHeight">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:maxWidth">@dimen/biometric_auth_pattern_view_max_size</item>
+        <item name="android:minHeight">@dimen/biometric_auth_pattern_view_size</item>
+        <item name="android:minWidth">@dimen/biometric_auth_pattern_view_size</item>
         <item name="android:paddingBottom">0dp</item>
         <item name="android:paddingHorizontal">0dp</item>
         <item name="android:paddingTop">0dp</item>
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
index 03bff7b..369f453 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
@@ -36,6 +36,7 @@
 import com.android.settings.Settings;
 import com.android.settings.SettingsActivity;
 import com.android.settings.SubSettings;
+import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
@@ -228,6 +229,8 @@
         addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
         addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
         addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
+        addActivityFilter(activityFilters, FaceEnrollIntroductionInternal.class);
+        addActivityFilter(activityFilters, Settings.FaceSettingsInternalActivity.class);
         addActivityFilter(activityFilters, AvatarPickerActivity.class);
         mSplitController.registerRule(new ActivityRule(activityFilters, true /* alwaysExpand */));
     }
diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
index 932c410..bb16f0b 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
@@ -48,6 +48,7 @@
 import com.android.settings.R;
 import com.android.settings.SetupWizardUtils;
 import com.android.settings.core.InstrumentedActivity;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockGeneric;
 import com.android.settings.password.ChooseLockPattern;
 import com.android.settings.password.ChooseLockSettingsHelper;
@@ -215,11 +216,16 @@
                 mIsFaceEnrollable =
                         faceManager.getEnrolledFaces(mUserId).size() < maxEnrolls;
 
-                // exclude face enrollment from setup wizard if configured as a convenience
-                // isSetupWizard is always false for unicorn enrollment, so if consent is
-                // required check if setup has completed instead.
-                final boolean isSettingUp = isSetupWizard || (mParentalOptionsRequired
+                final boolean parentalConsent = isSetupWizard || (mParentalOptionsRequired
                         && !WizardManagerHelper.isUserSetupComplete(this));
+                if (parentalConsent && isMultiSensor && mIsFaceEnrollable) {
+                    // Exclude face enrollment from setup wizard if feature config not supported
+                    // in setup wizard flow, we still allow user enroll faces through settings.
+                    mIsFaceEnrollable = FeatureFactory.getFactory(getApplicationContext())
+                            .getFaceFeatureProvider()
+                            .isSetupWizardSupported(getApplicationContext());
+                    Log.d(TAG, "config_suw_support_face_enroll: " + mIsFaceEnrollable);
+                }
             }
         }
         if (mHasFeatureFingerprint) {
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index eea1bad..2f852f0 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -38,7 +38,10 @@
 import com.android.settings.Utils;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
 import com.android.settings.core.InstrumentedActivity;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
+import com.android.systemui.unfold.updates.FoldProvider;
 
 import com.google.android.setupcompat.template.FooterBarMixin;
 import com.google.android.setupcompat.template.FooterButton;
@@ -60,8 +63,10 @@
     public static final String EXTRA_KEY_SENSOR_ID = "sensor_id";
     public static final String EXTRA_KEY_CHALLENGE = "challenge";
     public static final String EXTRA_KEY_MODALITY = "sensor_modality";
+    public static final String EXTRA_KEY_NEXT_LAUNCHED = "next_launched";
     public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face";
     public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint";
+    public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance";
 
     /**
      * Used by the choose fingerprint wizard to indicate the wizard is
@@ -115,14 +120,25 @@
      * example, when starting fingerprint enroll after face enroll.
      */
     public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6;
+    public static final int REQUEST_POSTURE_GUIDANCE = 7;
 
     protected boolean mLaunchedConfirmLock;
+    protected boolean mLaunchedPostureGuidance;
+    protected boolean mNextLaunched;
     protected byte[] mToken;
     protected int mUserId;
     protected int mSensorId;
+    @BiometricUtils.DevicePostureInt
+    protected int mDevicePostureState;
     protected long mChallenge;
     protected boolean mFromSettingsSummary;
     protected FooterBarMixin mFooterBarMixin;
+    @Nullable
+    protected ScreenSizeFoldProvider mScreenSizeFoldProvider;
+    @Nullable
+    protected Intent mPostureGuidanceIntent = null;
+    @Nullable
+    protected FoldProvider.FoldCallback mFoldCallback = null;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -139,16 +155,23 @@
                     ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
         }
         mFromSettingsSummary = getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false);
-        if (savedInstanceState != null && mToken == null) {
-            mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM);
-            mToken = savedInstanceState.getByteArray(
-                    ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
-            mFromSettingsSummary =
-                    savedInstanceState.getBoolean(EXTRA_FROM_SETTINGS_SUMMARY, false);
-            mChallenge = savedInstanceState.getLong(EXTRA_KEY_CHALLENGE);
-            mSensorId = savedInstanceState.getInt(EXTRA_KEY_SENSOR_ID);
+        if (savedInstanceState != null) {
+            if (mToken == null) {
+                mLaunchedConfirmLock = savedInstanceState.getBoolean(EXTRA_KEY_LAUNCHED_CONFIRM);
+                mToken = savedInstanceState.getByteArray(
+                        ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
+                mFromSettingsSummary =
+                        savedInstanceState.getBoolean(EXTRA_FROM_SETTINGS_SUMMARY, false);
+                mChallenge = savedInstanceState.getLong(EXTRA_KEY_CHALLENGE);
+                mSensorId = savedInstanceState.getInt(EXTRA_KEY_SENSOR_ID);
+            }
+            mLaunchedPostureGuidance = savedInstanceState.getBoolean(
+                    EXTRA_LAUNCHED_POSTURE_GUIDANCE);
+            mNextLaunched = savedInstanceState.getBoolean(EXTRA_KEY_NEXT_LAUNCHED);
         }
         mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
+        mPostureGuidanceIntent = FeatureFactory.getFactory(getApplicationContext())
+                .getFaceFeatureProvider().getPostureGuidanceIntent(getApplicationContext());
     }
 
     @Override
@@ -159,6 +182,8 @@
         outState.putBoolean(EXTRA_FROM_SETTINGS_SUMMARY, mFromSettingsSummary);
         outState.putLong(EXTRA_KEY_CHALLENGE, mChallenge);
         outState.putInt(EXTRA_KEY_SENSOR_ID, mSensorId);
+        outState.putBoolean(EXTRA_LAUNCHED_POSTURE_GUIDANCE, mLaunchedPostureGuidance);
+        outState.putBoolean(EXTRA_KEY_NEXT_LAUNCHED, mNextLaunched);
     }
 
     @Override
@@ -184,6 +209,12 @@
     @Override
     protected void onStop() {
         super.onStop();
+        if (mScreenSizeFoldProvider != null && mFoldCallback != null) {
+            mScreenSizeFoldProvider.unregisterCallback(mFoldCallback);
+        }
+        mScreenSizeFoldProvider = null;
+        mFoldCallback = null;
+
         if (!isChangingConfigurations() && shouldFinishWhenBackgrounded()
                 && !BiometricUtils.isAnyMultiBiometricFlow(this)) {
             setResult(RESULT_TIMEOUT);
@@ -191,6 +222,17 @@
         }
     }
 
+    protected boolean launchPostureGuidance() {
+        if (mPostureGuidanceIntent == null || mLaunchedPostureGuidance) {
+            return false;
+        }
+        BiometricUtils.copyMultiBiometricExtras(getIntent(), mPostureGuidanceIntent);
+        startActivityForResult(mPostureGuidanceIntent, REQUEST_POSTURE_GUIDANCE);
+        mLaunchedPostureGuidance = true;
+        overridePendingTransition(0 /* no enter anim */, 0 /* no exit anim */);
+        return mLaunchedPostureGuidance;
+    }
+
     protected boolean shouldFinishWhenBackgrounded() {
         return !WizardManagerHelper.isAnySetupWizard(getIntent());
     }
diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
index acfe5a1..730e049 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
@@ -155,6 +155,8 @@
         if (savedInstanceState != null) {
             mConfirmingCredentials = savedInstanceState.getBoolean(KEY_CONFIRMING_CREDENTIALS);
             mHasScrolledToBottom = savedInstanceState.getBoolean(KEY_SCROLLED_TO_BOTTOM);
+            mLaunchedPostureGuidance = savedInstanceState.getBoolean(
+                    EXTRA_LAUNCHED_POSTURE_GUIDANCE);
         }
 
         Intent intent = getIntent();
@@ -273,6 +275,7 @@
                 finish();
             }
         }
+        mNextLaunched = true;
     }
 
     private void launchChooseLock() {
diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java
index 9cc656c..7333083 100644
--- a/src/com/android/settings/biometrics/BiometricUtils.java
+++ b/src/com/android/settings/biometrics/BiometricUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.biometrics;
 
+import android.annotation.IntDef;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
@@ -47,17 +48,53 @@
 
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Common biometric utilities.
  */
 public class BiometricUtils {
     private static final String TAG = "BiometricUtils";
 
+    // Note: Theis IntDef must align SystemUI DevicePostureInt
+    @IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
+            DEVICE_POSTURE_UNKNOWN,
+            DEVICE_POSTURE_CLOSED,
+            DEVICE_POSTURE_HALF_OPENED,
+            DEVICE_POSTURE_OPENED,
+            DEVICE_POSTURE_FLIPPED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DevicePostureInt {}
+
+    // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we
+    // use the Device State -> Jetpack Posture map in DevicePostureControllerImpl to translate
+    // between the two.
+    public static final int DEVICE_POSTURE_UNKNOWN = 0;
+    public static final int DEVICE_POSTURE_CLOSED = 1;
+    public static final int DEVICE_POSTURE_HALF_OPENED = 2;
+    public static final int DEVICE_POSTURE_OPENED = 3;
+    public static final int DEVICE_POSTURE_FLIPPED = 4;
+
+    public static int sAllowEnrollPosture = DEVICE_POSTURE_UNKNOWN;
+
     /**
      * Request was sent for starting another enrollment of a previously
      * enrolled biometric of the same type.
      */
     public static int REQUEST_ADD_ANOTHER = 7;
+
+    /**
+     * Gatekeeper credential not match exception, it throws if VerifyCredentialResponse is not
+     * matched in requestGatekeeperHat().
+     */
+    public static class GatekeeperCredentialNotMatchException extends IllegalStateException {
+        public GatekeeperCredentialNotMatchException(String s) {
+            super(s);
+        }
+    };
+
     /**
      * Given the result from confirming or choosing a credential, request Gatekeeper to generate
      * a HardwareAuthToken with the Gatekeeper Password together with a biometric challenge.
@@ -67,6 +104,8 @@
      * @param userId User ID that the credential/biometric operation applies to
      * @param challenge Unique biometric challenge from FingerprintManager/FaceManager
      * @return
+     * @throws GatekeeperCredentialNotMatchException if Gatekeeper response is not match
+     * @throws IllegalStateException if Gatekeeper Password is missing
      */
     public static byte[] requestGatekeeperHat(@NonNull Context context, @NonNull Intent result,
             int userId, long challenge) {
@@ -84,7 +123,7 @@
         final VerifyCredentialResponse response = utils.verifyGatekeeperPasswordHandle(gkPwHandle,
                 challenge, userId);
         if (!response.isMatched()) {
-            throw new IllegalStateException("Unable to request Gatekeeper HAT");
+            throw new GatekeeperCredentialNotMatchException("Unable to request Gatekeeper HAT");
         }
         return response.getGatekeeperHAT();
     }
@@ -270,6 +309,51 @@
                 || isMultiBiometricFingerprintEnrollmentFlow(activity);
     }
 
+    /**
+     * Used to check if the activity is showing a posture guidance to user.
+     *
+     * @param devicePosture the device posture state
+     * @param isLaunchedPostureGuidance True launching a posture guidance to user
+     * @return True if the activity is showing posture guidance to user
+     */
+    public static boolean isPostureGuidanceShowing(@DevicePostureInt int devicePosture,
+            boolean isLaunchedPostureGuidance) {
+        return !isPostureAllowEnrollment(devicePosture) && isLaunchedPostureGuidance;
+    }
+
+    /**
+     * Used to check if current device posture state is allow to enroll biometrics.
+     * For compatibility, we don't restrict enrollment if device do not config.
+     *
+     * @param devicePosture True if current device posture allow enrollment
+     * @return True if current device posture state allow enrollment
+     */
+    public static boolean isPostureAllowEnrollment(@DevicePostureInt int devicePosture) {
+        return (sAllowEnrollPosture == DEVICE_POSTURE_UNKNOWN)
+                || (devicePosture == sAllowEnrollPosture);
+    }
+
+    /**
+     * Used to check if the activity should show a posture guidance to user.
+     *
+     * @param devicePosture the device posture state
+     * @param isLaunchedPostureGuidance True launching a posture guidance to user
+     * @return True if posture disallow enroll and posture guidance not showing, false otherwise.
+     */
+    public static boolean shouldShowPostureGuidance(@DevicePostureInt int devicePosture,
+            boolean isLaunchedPostureGuidance) {
+        return !isPostureAllowEnrollment(devicePosture) && !isLaunchedPostureGuidance;
+    }
+
+    /**
+     * Sets allowed device posture for face enrollment.
+     *
+     * @param devicePosture the allowed posture state {@link DevicePostureInt} for enrollment
+     */
+    public static void setDevicePosturesAllowEnroll(@DevicePostureInt int devicePosture) {
+        sAllowEnrollPosture = devicePosture;
+    }
+
     public static void copyMultiBiometricExtras(@NonNull Intent fromIntent,
             @NonNull Intent toIntent) {
         PendingIntent pendingIntent = (PendingIntent) fromIntent.getExtra(
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
index d2d356b..4ef4752 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEducation.java
@@ -16,24 +16,35 @@
 
 package com.android.settings.biometrics.face;
 
+import static com.android.settings.biometrics.BiometricUtils.isPostureAllowEnrollment;
+import static com.android.settings.biometrics.BiometricUtils.isPostureGuidanceShowing;
+
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.hardware.face.FaceManager;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
 import android.widget.CompoundButton;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricEnrollBase;
 import com.android.settings.biometrics.BiometricUtils;
 import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settings.password.SetupSkipDialog;
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
+import com.android.systemui.unfold.updates.FoldProvider;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.google.android.setupcompat.template.FooterBarMixin;
@@ -41,18 +52,19 @@
 import com.google.android.setupcompat.util.WizardManagerHelper;
 import com.google.android.setupdesign.view.IllustrationVideoView;
 
+/**
+ * Provides animated education for users to know how to enroll a face with appropriate posture.
+ */
 public class FaceEnrollEducation extends BiometricEnrollBase {
     private static final String TAG = "FaceEducation";
 
     private FaceManager mFaceManager;
     private FaceEnrollAccessibilityToggle mSwitchDiversity;
-
     private boolean mIsUsingLottie;
     private IllustrationVideoView mIllustrationDefault;
     private LottieAnimationView mIllustrationLottie;
     private View mIllustrationAccessibility;
     private Intent mResultIntent;
-    private boolean mNextClicked;
     private boolean mAccessibilityEnabled;
 
     private final CompoundButton.OnCheckedChangeListener mSwitchDiversityListener =
@@ -155,6 +167,34 @@
     }
 
     @Override
+    protected void onStart() {
+        super.onStart();
+        if (getPostureGuidanceIntent() == null) {
+            Log.d(TAG, "Device do not support posture guidance");
+            return;
+        }
+
+        BiometricUtils.setDevicePosturesAllowEnroll(
+                getResources().getInteger(R.integer.config_face_enroll_supported_posture));
+
+        if (getPostureCallback() == null) {
+            mFoldCallback = isFolded -> {
+                mDevicePostureState = isFolded ? BiometricUtils.DEVICE_POSTURE_CLOSED
+                        : BiometricUtils.DEVICE_POSTURE_OPENED;
+                if (BiometricUtils.shouldShowPostureGuidance(mDevicePostureState,
+                        mLaunchedPostureGuidance) && !mNextLaunched) {
+                    launchPostureGuidance();
+                }
+            };
+        }
+
+        if (mScreenSizeFoldProvider == null) {
+            mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
+            mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor());
+        }
+    }
+
+    @Override
     protected void onResume() {
         super.onResume();
         mSwitchDiversityListener.onCheckedChanged(mSwitchDiversity.getSwitch(),
@@ -172,7 +212,8 @@
 
     @Override
     protected boolean shouldFinishWhenBackgrounded() {
-        return super.shouldFinishWhenBackgrounded() && !mNextClicked;
+        return super.shouldFinishWhenBackgrounded() && !mNextLaunched
+                && !isPostureGuidanceShowing(mDevicePostureState, mLaunchedPostureGuidance);
     }
 
     @Override
@@ -206,13 +247,14 @@
             FaceEnrollAccessibilityDialog dialog = FaceEnrollAccessibilityDialog.newInstance();
             dialog.setPositiveButtonListener((dialog1, which) -> {
                 startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
-                mNextClicked = true;
+                mNextLaunched = true;
             });
             dialog.show(getSupportFragmentManager(), FaceEnrollAccessibilityDialog.class.getName());
         } else {
             startActivityForResult(intent, BIOMETRIC_FIND_SENSOR_REQUEST);
-            mNextClicked = true;
+            mNextLaunched = true;
         }
+
     }
 
     protected void onSkipButtonClick(View view) {
@@ -224,14 +266,28 @@
     }
 
     @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mScreenSizeFoldProvider != null && getPostureCallback() != null) {
+            mScreenSizeFoldProvider.onConfigurationChange(newConfig);
+        }
+    }
+
+    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_POSTURE_GUIDANCE) {
+            mLaunchedPostureGuidance = false;
+            if (resultCode == RESULT_CANCELED || resultCode == RESULT_SKIP) {
+                onSkipButtonClick(getCurrentFocus());
+            }
+            return;
+        }
         mResultIntent = data;
         boolean hasEnrolledFace = false;
         if (data != null) {
             hasEnrolledFace = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FACE, false);
         }
-        if (resultCode == RESULT_TIMEOUT) {
+        if (resultCode == RESULT_TIMEOUT || !isPostureAllowEnrollment(mDevicePostureState)) {
             setResult(resultCode, data);
             finish();
         } else if (requestCode == BIOMETRIC_FIND_SENSOR_REQUEST
@@ -243,6 +299,26 @@
                 finish();
             }
         }
+        mNextLaunched = false;
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected Intent getPostureGuidanceIntent() {
+        return mPostureGuidanceIntent;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected FoldProvider.FoldCallback getPostureCallback() {
+        return mFoldCallback;
+    }
+
+    @VisibleForTesting
+    @BiometricUtils.DevicePostureInt
+    protected int getDevicePostureState() {
+        return mDevicePostureState;
     }
 
     @Override
@@ -262,8 +338,10 @@
 
     private void showDefaultIllustration() {
         if (mIsUsingLottie) {
+            mIllustrationLottie.setAnimation(R.raw.face_education_lottie);
             mIllustrationLottie.setVisibility(View.VISIBLE);
             mIllustrationLottie.playAnimation();
+            mIllustrationLottie.setProgress(0f);
         } else {
             mIllustrationDefault.setVisibility(View.VISIBLE);
             mIllustrationDefault.start();
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index ed74d2a..a123308 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -18,9 +18,12 @@
 
 import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED;
 
+import static com.android.settings.biometrics.BiometricUtils.GatekeeperCredentialNotMatchException;
+
 import android.app.admin.DevicePolicyManager;
 import android.app.settings.SettingsEnums;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.face.FaceManager;
@@ -36,6 +39,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.R;
 import com.android.settings.Utils;
@@ -43,11 +47,12 @@
 import com.android.settings.biometrics.BiometricEnrollIntroduction;
 import com.android.settings.biometrics.BiometricUtils;
 import com.android.settings.biometrics.MultiBiometricEnrollHelper;
-import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settings.password.SetupSkipDialog;
 import com.android.settings.utils.SensorPrivacyManagerHelper;
 import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
+import com.android.systemui.unfold.updates.FoldProvider;
 
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -61,7 +66,6 @@
     private static final String TAG = "FaceEnrollIntroduction";
 
     private FaceManager mFaceManager;
-    private FaceFeatureProvider mFaceFeatureProvider;
     @Nullable private FooterButton mPrimaryFooterButton;
     @Nullable private FooterButton mSecondaryFooterButton;
     @Nullable private SensorPrivacyManager mSensorPrivacyManager;
@@ -99,6 +103,12 @@
     }
 
     @Override
+    protected boolean shouldFinishWhenBackgrounded() {
+        return super.shouldFinishWhenBackgrounded() && !BiometricUtils.isPostureGuidanceShowing(
+                mDevicePostureState, mLaunchedPostureGuidance);
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -142,9 +152,7 @@
             infoMessageRequireEyes.setText(getInfoMessageRequireEyes());
         }
 
-        mFaceManager = Utils.getFaceManagerOrNull(this);
-        mFaceFeatureProvider = FeatureFactory.getFactory(getApplicationContext())
-                .getFaceFeatureProvider();
+        mFaceManager = getFaceManager();
 
         // This path is an entry point for SetNewPasswordController, e.g.
         // adb shell am start -a android.app.action.SET_NEW_PASSWORD
@@ -154,11 +162,22 @@
                 // We either block on generateChallenge, or need to gray out the "next" button until
                 // the challenge is ready. Let's just do this for now.
                 mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
-                    mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId,
-                            challenge);
-                    mSensorId = sensorId;
-                    mChallenge = challenge;
-                    mFooterBarMixin.getPrimaryButton().setEnabled(true);
+                    if (isFinishing()) {
+                        // Do nothing if activity is finishing
+                        Log.w(TAG, "activity finished before challenge callback launched.");
+                        return;
+                    }
+
+                    try {
+                        mToken = requestGatekeeperHat(challenge);
+                        mSensorId = sensorId;
+                        mChallenge = challenge;
+                        mFooterBarMixin.getPrimaryButton().setEnabled(true);
+                    } catch (GatekeeperCredentialNotMatchException e) {
+                        // Let BiometricEnrollBase#onCreate() to trigger confirmLock()
+                        getIntent().removeExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE);
+                        recreate();
+                    }
                 });
             }
         }
@@ -172,8 +191,82 @@
         Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled);
     }
 
+    @VisibleForTesting
+    @Nullable
+    protected FaceManager getFaceManager() {
+        return Utils.getFaceManagerOrNull(this);
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected Intent getPostureGuidanceIntent() {
+        return mPostureGuidanceIntent;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected FoldProvider.FoldCallback getPostureCallback() {
+        return mFoldCallback;
+    }
+
+    @VisibleForTesting
+    @BiometricUtils.DevicePostureInt
+    protected int getDevicePostureState() {
+        return mDevicePostureState;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected byte[] requestGatekeeperHat(long challenge) {
+        return BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mScreenSizeFoldProvider != null && getPostureCallback() != null) {
+            mScreenSizeFoldProvider.onConfigurationChange(newConfig);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        if (getPostureGuidanceIntent() == null) {
+            Log.d(TAG, "Device do not support posture guidance");
+            return;
+        }
+
+        BiometricUtils.setDevicePosturesAllowEnroll(
+                getResources().getInteger(R.integer.config_face_enroll_supported_posture));
+
+        if (getPostureCallback() == null) {
+            mFoldCallback = isFolded -> {
+                mDevicePostureState = isFolded ? BiometricUtils.DEVICE_POSTURE_CLOSED
+                        : BiometricUtils.DEVICE_POSTURE_OPENED;
+                if (BiometricUtils.shouldShowPostureGuidance(mDevicePostureState,
+                        mLaunchedPostureGuidance) && !mNextLaunched) {
+                    launchPostureGuidance();
+                }
+            };
+        }
+
+        if (mScreenSizeFoldProvider == null) {
+            mScreenSizeFoldProvider = new ScreenSizeFoldProvider(getApplicationContext());
+            mScreenSizeFoldProvider.registerCallback(mFoldCallback, getMainExecutor());
+        }
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_POSTURE_GUIDANCE) {
+            mLaunchedPostureGuidance = false;
+            if (resultCode == RESULT_CANCELED || resultCode == RESULT_SKIP) {
+                onSkipButtonClick(getCurrentFocus());
+            }
+            return;
+        }
+
         // If user has skipped or finished enrolling, don't restart enrollment.
         final boolean isEnrollRequest = requestCode == BIOMETRIC_FIND_SENSOR_REQUEST
                 || requestCode == ENROLL_NEXT_BIOMETRIC_REQUEST;
@@ -184,10 +277,12 @@
             hasEnrolledFace = data.getBooleanExtra(EXTRA_FINISHED_ENROLL_FACE, false);
         }
 
-        if (resultCode == RESULT_CANCELED && hasEnrolledFace) {
-            setResult(resultCode, data);
-            finish();
-            return;
+        if (resultCode == RESULT_CANCELED) {
+            if (hasEnrolledFace || !BiometricUtils.isPostureAllowEnrollment(mDevicePostureState)) {
+                setResult(resultCode, data);
+                finish();
+                return;
+            }
         }
 
         if (isEnrollRequest && isResultSkipOrFinished || hasEnrolledFace) {
diff --git a/src/com/android/settings/biometrics/face/FaceFeatureProvider.java b/src/com/android/settings/biometrics/face/FaceFeatureProvider.java
index 26ea261..1a4fd90 100644
--- a/src/com/android/settings/biometrics/face/FaceFeatureProvider.java
+++ b/src/com/android/settings/biometrics/face/FaceFeatureProvider.java
@@ -17,9 +17,19 @@
 package com.android.settings.biometrics.face;
 
 import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
 
 /** Feature provider for face unlock */
 public interface FaceFeatureProvider {
+    /** Returns specified intent config by resource R.string.config_face_enroll_guidance_page. */
+    @Nullable
+    Intent getPostureGuidanceIntent(Context context);
+
     /** Returns true if attention checking is supported. */
     boolean isAttentionSupported(Context context);
+
+    /** Returns true if setup wizard supported face enrollment. */
+    boolean isSetupWizardSupported(Context context);
 }
diff --git a/src/com/android/settings/biometrics/face/FaceFeatureProviderImpl.java b/src/com/android/settings/biometrics/face/FaceFeatureProviderImpl.java
index e508600..8b7edce 100644
--- a/src/com/android/settings/biometrics/face/FaceFeatureProviderImpl.java
+++ b/src/com/android/settings/biometrics/face/FaceFeatureProviderImpl.java
@@ -16,13 +16,48 @@
 
 package com.android.settings.biometrics.face;
 
+import android.content.ComponentName;
 import android.content.Context;
-import android.provider.Settings;
+import android.content.Intent;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
 
 public class FaceFeatureProviderImpl implements FaceFeatureProvider {
 
+    /**
+     * Returns the guidance page intent if device support {@link FoldingFeature}, and we want to
+     * guide user enrolling faces with specific device posture.
+     *
+     * @param context the application context
+     * @return the posture guidance intent, otherwise null if device not support
+     */
+    @Nullable
+    @Override
+    public Intent getPostureGuidanceIntent(Context context) {
+        final String flattenedString = context.getString(R.string.config_face_enroll_guidance_page);
+        final Intent intent;
+        if (!TextUtils.isEmpty(flattenedString)) {
+            ComponentName componentName = ComponentName.unflattenFromString(flattenedString);
+            if (componentName != null) {
+                intent = new Intent();
+                intent.setComponent(componentName);
+                return intent;
+            }
+        }
+        return null;
+    }
+
     @Override
     public boolean isAttentionSupported(Context context) {
         return true;
     }
+
+    @Override
+    public boolean isSetupWizardSupported(@NonNull Context context) {
+        return true;
+    }
 }
diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java
index c39ef66..07c533f 100644
--- a/src/com/android/settings/password/ChooseLockPattern.java
+++ b/src/com/android/settings/password/ChooseLockPattern.java
@@ -215,6 +215,7 @@
         private FooterButton mNextButton;
         @VisibleForTesting protected LockscreenCredential mChosenPattern;
         private ColorStateList mDefaultHeaderColorList;
+        private View mSudContent;
 
         /**
          * The patten used during the help screen to show how to draw a pattern.
@@ -537,6 +538,10 @@
             );
             mSkipOrClearButton = mixin.getSecondaryButton();
             mNextButton = mixin.getPrimaryButton();
+            // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
+            mSudContent = layout.findViewById(R.id.sud_layout_content);
+            mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
+                    0);
 
             return layout;
         }
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index 1062d94..b4f0aa3 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -100,6 +100,7 @@
         private CountDownTimer mCountdownTimer;
 
         private GlifLayout mGlifLayout;
+        private View mSudContent;
 
         // caller-supplied text for various prompts
         private CharSequence mHeaderText;
@@ -129,7 +130,10 @@
             mGlifLayout = view.findViewById(R.id.setup_wizard_layout);
             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
             mErrorTextView = (TextView) view.findViewById(R.id.errorText);
-
+            // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
+            mSudContent = mGlifLayout.findViewById(R.id.sud_layout_content);
+            mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
+                    0);
             mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
 
             // make it so unhandled touch events within the unlock screen go to the
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollEducationTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollEducationTest.java
new file mode 100644
index 0000000..b4dddde
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollEducationTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2023 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.biometrics.face;
+
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+import static android.util.DisplayMetrics.DENSITY_XXXHIGH;
+
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_NEXT_LAUNCHED;
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_LAUNCHED_POSTURE_GUIDANCE;
+import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_CLOSED;
+import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_OPENED;
+import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.face.FaceManager;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowUtils;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowUtils.class})
+public class FaceEnrollEducationTest {
+    @Mock
+    private FaceManager mFaceManager;
+
+    private Context mContext;
+    private ActivityController<TestFaceEnrollEducation> mActivityController;
+    private TestFaceEnrollEducation mActivity;
+    private FakeFeatureFactory mFakeFeatureFactory;
+
+    public static class TestFaceEnrollEducation extends FaceEnrollEducation {
+
+        @Override
+        protected boolean launchPostureGuidance() {
+            return super.launchPostureGuidance();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowUtils.setFaceManager(mFaceManager);
+        mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowUtils.reset();
+    }
+
+    private void setupActivityForPosture() {
+        final Intent testIntent = new Intent();
+        // Set the challenge token so the confirm screen will not be shown
+        testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
+        testIntent.putExtra(EXTRA_KEY_NEXT_LAUNCHED, false);
+        testIntent.putExtra(EXTRA_LAUNCHED_POSTURE_GUIDANCE, false);
+
+        when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
+                testIntent);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mActivityController = Robolectric.buildActivity(
+                TestFaceEnrollEducation.class, testIntent);
+        mActivity = spy(mActivityController.create().get());
+
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+    }
+
+    private void setupActivity() {
+        final Intent testIntent = new Intent();
+        // Set the challenge token so the confirm screen will not be shown
+        testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
+
+        when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
+                null /* Simulate no posture intent */);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mActivityController = Robolectric.buildActivity(
+                TestFaceEnrollEducation.class, testIntent);
+        mActivity = spy(mActivityController.create().get());
+
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+    }
+
+    private GlifLayout getGlifLayout() {
+        return mActivity.findViewById(R.id.setup_wizard_layout);
+    }
+
+    @Test
+    public void testFaceEnrollEducation_hasHeader() {
+        setupActivity();
+        CharSequence headerText = getGlifLayout().getHeaderText();
+
+        assertThat(headerText.toString()).isEqualTo(
+                mContext.getString(R.string.security_settings_face_enroll_education_title));
+    }
+
+    @Test
+    public void testFaceEnrollEducation_hasDescription() {
+        setupActivity();
+        CharSequence desc = getGlifLayout().getDescriptionText();
+
+        assertThat(desc.toString()).isEqualTo(
+                mContext.getString(R.string.security_settings_face_enroll_education_message));
+    }
+
+    @Test
+    public void testFaceEnrollEducation_showFooterPrimaryButton() {
+        setupActivity();
+        FooterBarMixin footer = getGlifLayout().getMixin(FooterBarMixin.class);
+        FooterButton footerButton = footer.getPrimaryButton();
+
+        assertThat(footerButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(footerButton.getText().toString()).isEqualTo(
+                mContext.getString(R.string.security_settings_face_enroll_education_start));
+    }
+
+    @Test
+    public void testFaceEnrollEducation_showFooterSecondaryButton() {
+        setupActivity();
+        FooterBarMixin footer = getGlifLayout().getMixin(FooterBarMixin.class);
+        FooterButton footerButton = footer.getSecondaryButton();
+
+        assertThat(footerButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(footerButton.getText().toString()).isEqualTo(mContext.getString(
+                R.string.security_settings_face_enroll_introduction_cancel));
+    }
+
+    @Test
+    public void testFaceEnrollEducation_defaultNeverLaunchPostureGuidance() {
+        setupActivity();
+
+        assertThat(mActivity.launchPostureGuidance()).isFalse();
+        assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
+    }
+
+    @Test
+    public void testFaceEnrollEducation_onStartNeverRegisterPostureChangeCallback() {
+        setupActivity();
+        mActivity.onStart();
+
+        assertThat(mActivity.getPostureGuidanceIntent()).isNull();
+        assertThat(mActivity.getPostureCallback()).isNull();
+        assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
+    }
+
+    @Test
+    public void testFaceEnrollEducationWithPosture_onStartRegisteredPostureChangeCallback() {
+        setupActivityForPosture();
+        mActivity.onStart();
+
+        assertThat(mActivity.getPostureGuidanceIntent()).isNotNull();
+        assertThat(mActivity.getPostureCallback()).isNotNull();
+    }
+
+    @Test
+    public void testFaceEnrollEducationWithPosture_onFoldedUpdated_unFolded() {
+        final Configuration newConfig = new Configuration();
+        newConfig.smallestScreenWidthDp = DENSITY_XXXHIGH;
+        setupActivityForPosture();
+        mActivity.onStart();
+
+        assertThat(mActivity.getPostureGuidanceIntent()).isNotNull();
+        assertThat(mActivity.getPostureCallback()).isNotNull();
+
+        mActivity.onConfigurationChanged(newConfig);
+
+        assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_OPENED);
+    }
+
+    @Test
+    public void testFaceEnrollEducationWithPosture_onFoldedUpdated_folded() {
+        final Configuration newConfig = new Configuration();
+        newConfig.smallestScreenWidthDp = DENSITY_DEFAULT;
+        setupActivityForPosture();
+        mActivity.onStart();
+
+        assertThat(mActivity.getPostureGuidanceIntent()).isNotNull();
+        assertThat(mActivity.getPostureCallback()).isNotNull();
+
+        mActivity.onConfigurationChanged(newConfig);
+
+        assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_CLOSED);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java
new file mode 100644
index 0000000..6c04add
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2022 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.biometrics.face;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+import static android.util.DisplayMetrics.DENSITY_XXXHIGH;
+
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_NEXT_LAUNCHED;
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_LAUNCHED_POSTURE_GUIDANCE;
+import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_CLOSED;
+import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_OPENED;
+import static com.android.settings.biometrics.BiometricUtils.DEVICE_POSTURE_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.os.UserHandle;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
+import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
+import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager;
+import com.android.settings.testutils.shadow.ShadowUserManager;
+import com.android.settings.testutils.shadow.ShadowUtils;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+import com.google.android.setupdesign.view.BottomScrollView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowLockPatternUtils.class,
+        ShadowUserManager.class,
+        ShadowUtils.class,
+        ShadowDevicePolicyManager.class,
+        ShadowSensorPrivacyManager.class
+})
+public class FaceEnrollIntroductionTest {
+
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private LockPatternUtils mLockPatternUtils;
+
+    private Context mContext;
+    private ActivityController<? extends Activity> mController;
+    private TestFaceEnrollIntroduction mActivity;
+    private FaceEnrollIntroduction mSpyActivity;
+    private FakeFeatureFactory mFakeFeatureFactory;
+    private ShadowUserManager mUserManager;
+
+    enum GateKeeperAction { CALL_SUPER, RETURN_BYTE_ARRAY, THROW_CREDENTIAL_NOT_MATCH }
+
+    public static class TestFaceEnrollIntroduction extends FaceEnrollIntroduction {
+
+        private int mRecreateCount = 0;
+
+        public int getRecreateCount() {
+            return mRecreateCount;
+        }
+
+        @Override
+        public void recreate() {
+            mRecreateCount++;
+            // Do nothing
+        }
+
+        public boolean getConfirmingCredentials() {
+            return mConfirmingCredentials;
+        }
+
+        public FaceManager mOverrideFaceManager = null;
+        @NonNull
+        public GateKeeperAction mGateKeeperAction = GateKeeperAction.CALL_SUPER;
+
+        @Nullable
+        @Override
+        public byte[] requestGatekeeperHat(long challenge) {
+            switch (mGateKeeperAction) {
+                case RETURN_BYTE_ARRAY:
+                    return new byte[]{1};
+                case THROW_CREDENTIAL_NOT_MATCH:
+                    throw new BiometricUtils.GatekeeperCredentialNotMatchException("test");
+                case CALL_SUPER:
+                default:
+                    return super.requestGatekeeperHat(challenge);
+            }
+        }
+
+        @Nullable
+        @Override
+        protected FaceManager getFaceManager() {
+            return mOverrideFaceManager;
+        }
+
+        @Override
+        protected boolean launchPostureGuidance() {
+            return super.launchPostureGuidance();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowUtils.setFaceManager(mFaceManager);
+        mUserManager = ShadowUserManager.getShadow();
+        mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
+
+        when(mFakeFeatureFactory.securityFeatureProvider.getLockPatternUtils(any(Context.class)))
+                .thenReturn(mLockPatternUtils);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowUtils.reset();
+    }
+
+    private void setupActivity() {
+        final Intent testIntent = new Intent();
+        // Set the challenge token so the confirm screen will not be shown
+        testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
+
+        when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
+                null /* Simulate no posture intent */);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mUserManager.addUserProfile(new UserHandle(0));
+        mController = Robolectric.buildActivity(
+                TestFaceEnrollIntroduction.class, testIntent);
+        mActivity = (TestFaceEnrollIntroduction) spy(mController.get());
+        mActivity.mOverrideFaceManager = mFaceManager;
+        when(mActivity.getPostureGuidanceIntent()).thenReturn(null);
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mLockPatternUtils.getActivePasswordQuality(Mockito.anyInt())).thenReturn(
+                PASSWORD_QUALITY_NUMERIC);
+
+        mController.create();
+    }
+
+    private void setupActivityForPosture() {
+        final Intent testIntent = new Intent();
+        // Set the challenge token so the confirm screen will not be shown
+        testIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
+        testIntent.putExtra(EXTRA_KEY_NEXT_LAUNCHED, false);
+        testIntent.putExtra(EXTRA_LAUNCHED_POSTURE_GUIDANCE, false);
+
+        when(mFakeFeatureFactory.mFaceFeatureProvider.getPostureGuidanceIntent(any())).thenReturn(
+                testIntent);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mUserManager.addUserProfile(new UserHandle(0));
+        mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, testIntent);
+        mSpyActivity = (FaceEnrollIntroduction) spy(mController.get());
+        when(mSpyActivity.getPostureGuidanceIntent()).thenReturn(testIntent);
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mLockPatternUtils.getActivePasswordQuality(Mockito.anyInt())).thenReturn(
+                PASSWORD_QUALITY_NUMERIC);
+
+        mController.create();
+    }
+
+    private void setupActivityWithGenerateChallenge(@NonNull Intent intent) {
+        doAnswer(invocation -> {
+            final FaceManager.GenerateChallengeCallback callback =
+                    invocation.getArgument(1);
+            callback.onGenerateChallengeResult(0, 0, 1L);
+            return null;
+        }).when(mFaceManager).generateChallenge(anyInt(), any());
+        mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent);
+        mActivity = (TestFaceEnrollIntroduction) mController.get();
+        mActivity.mOverrideFaceManager = mFaceManager;
+    }
+
+    private GlifLayout getGlifLayout(Activity activity) {
+        return activity.findViewById(R.id.setup_wizard_layout);
+    }
+
+    private void setFaceManagerToHave(int numEnrollments) {
+        List<Face> faces = new ArrayList<>();
+        for (int i = 0; i < numEnrollments; i++) {
+            faces.add(new Face("Face " + i /* name */, 1 /*faceId */, 1 /* deviceId */));
+        }
+        when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(faces);
+    }
+
+    @Test
+    public void intro_CheckCanEnroll() {
+        setFaceManagerToHave(0 /* numEnrollments */);
+        setupActivityWithGenerateChallenge(new Intent());
+        mController.create();
+        int result = mActivity.checkMaxEnrolled();
+
+        assertThat(result).isEqualTo(0);
+    }
+
+    @Test
+    public void intro_CheckMaxEnrolled() {
+        setFaceManagerToHave(1 /* numEnrollments */);
+        setupActivityWithGenerateChallenge(new Intent());
+        mController.create();
+        int result = mActivity.checkMaxEnrolled();
+
+        assertThat(result).isEqualTo(R.string.face_intro_error_max);
+    }
+
+    @Test
+    public void testOnCreate() {
+        setupActivityWithGenerateChallenge(new Intent());
+        mController.create();
+    }
+
+    @Test
+    public void testOnCreateToGenerateChallenge() {
+        setupActivityWithGenerateChallenge(
+                new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
+        mActivity.mGateKeeperAction = GateKeeperAction.RETURN_BYTE_ARRAY;
+        mController.create();
+    }
+
+    @Test
+    public void testGenerateChallengeFailThenRecreate() {
+        setupActivityWithGenerateChallenge(
+                new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
+        mActivity.mGateKeeperAction = GateKeeperAction.THROW_CREDENTIAL_NOT_MATCH;
+        mController.create();
+
+        // Make sure recreate() is called on original activity
+        assertThat(mActivity.getRecreateCount()).isEqualTo(1);
+
+        // Simulate recreate() action
+        setupActivityWithGenerateChallenge(mActivity.getIntent());
+        mController.create();
+
+        // Verify confirmLock()
+        assertThat(mActivity.getConfirmingCredentials()).isTrue();
+        ShadowActivity shadowActivity = Shadows.shadowOf(mActivity);
+        ShadowActivity.IntentForResult startedActivity =
+                shadowActivity.getNextStartedActivityForResult();
+        assertWithMessage("Next activity 1").that(startedActivity).isNotNull();
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_hasHeader() {
+        setupActivity();
+        TextView headerTextView = getGlifLayout(mActivity).findViewById(R.id.suc_layout_title);
+
+        assertThat(headerTextView).isNotNull();
+        assertThat(headerTextView.getText().toString()).isNotEmpty();
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_hasDescription() {
+        setupActivity();
+        CharSequence desc = getGlifLayout(mActivity).getDescriptionText();
+
+        assertThat(desc.toString()).isEqualTo(
+                mContext.getString(R.string.security_settings_face_enroll_introduction_message));
+    }
+
+    @Test
+    public void testFaceEnrollEducation_hasBottomScrollView() {
+        setupActivity();
+        BottomScrollView scrollView = getGlifLayout(mActivity).findViewById(R.id.sud_scroll_view);
+
+        assertThat(scrollView).isNotNull();
+        assertThat(scrollView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_showFooterPrimaryButton() {
+        setupActivity();
+        FooterBarMixin footer = getGlifLayout(mActivity).getMixin(FooterBarMixin.class);
+        FooterButton footerButton = footer.getPrimaryButton();
+
+        assertThat(footerButton).isNotNull();
+        assertThat(footerButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(footerButton.getText().toString()).isEqualTo(
+                mContext.getString(R.string.security_settings_face_enroll_introduction_agree));
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_notShowFooterSecondaryButton() {
+        setupActivity();
+        FooterBarMixin footer = getGlifLayout(mActivity).getMixin(FooterBarMixin.class);
+        FooterButton footerButton = footer.getSecondaryButton();
+
+        assertThat(footerButton.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_defaultNeverLaunchPostureGuidance() {
+        setupActivity();
+
+        assertThat(mActivity.launchPostureGuidance()).isFalse();
+        assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_onStartNeverRegisterPostureChangeCallback() {
+        setupActivity();
+        mActivity.onStart();
+
+        assertThat(mActivity.getPostureGuidanceIntent()).isNull();
+        assertThat(mActivity.getPostureCallback()).isNull();
+        assertThat(mActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_UNKNOWN);
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_onStartRegisteredPostureChangeCallback() {
+        setupActivityForPosture();
+        mSpyActivity.onStart();
+
+        assertThat(mSpyActivity.getPostureGuidanceIntent()).isNotNull();
+        assertThat(mSpyActivity.getPostureCallback()).isNotNull();
+    }
+
+    @Test
+    public void testFaceEnrollIntroduction_onFoldedUpdated_unFolded() {
+        final Configuration newConfig = new Configuration();
+        newConfig.smallestScreenWidthDp = DENSITY_XXXHIGH;
+        setupActivityForPosture();
+        mSpyActivity.onStart();
+
+        assertThat(mSpyActivity.getPostureGuidanceIntent()).isNotNull();
+        assertThat(mSpyActivity.getPostureCallback()).isNotNull();
+
+        mSpyActivity.onConfigurationChanged(newConfig);
+
+        assertThat(mSpyActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_OPENED);
+    }
+
+    @Test
+    public void testFaceEnrollEducation_onFoldedUpdated_folded() {
+        final Configuration newConfig = new Configuration();
+        newConfig.smallestScreenWidthDp = DENSITY_DEFAULT;
+        setupActivityForPosture();
+        mSpyActivity.onStart();
+
+        assertThat(mSpyActivity.getPostureGuidanceIntent()).isNotNull();
+        assertThat(mSpyActivity.getPostureCallback()).isNotNull();
+
+        mSpyActivity.onConfigurationChanged(newConfig);
+
+        assertThat(mSpyActivity.getDevicePostureState()).isEqualTo(DEVICE_POSTURE_CLOSED);
+    }
+}