Merge "Refine A11y Tile Service javadoc" into tm-dev
diff --git a/res/drawable/accessibility_auto_added_qs_tooltips_illustration.xml b/res/drawable/accessibility_auto_added_qs_tooltips_illustration.xml
new file mode 100644
index 0000000..2583b6f
--- /dev/null
+++ b/res/drawable/accessibility_auto_added_qs_tooltips_illustration.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="178dp"
+    android:height="150dp"
+    android:viewportWidth="178"
+    android:viewportHeight="150">
+  <path
+      android:pathData="M161.612,147.208L16.619,147.208A13,13 0,0 1,3.619 134.208L3.619,-442.725A13,13 0,0 1,16.619 -455.725L161.612,-455.725A13,13 0,0 1,174.612 -442.725L174.612,134.208A13,13 0,0 1,161.612 147.208z"
+      android:strokeWidth="6"
+      android:fillColor="#ffffff"
+      android:strokeColor="#EDEDED"/>
+  <path
+      android:pathData="M28.47,103.945L48.47,103.945A12,12 0,0 1,60.47 115.945L60.47,115.945A12,12 0,0 1,48.47 127.945L28.47,127.945A12,12 0,0 1,16.47 115.945L16.47,115.945A12,12 0,0 1,28.47 103.945z"
+      android:fillColor="#797272"/>
+  <path
+      android:pathData="M38.5,115.5m-15.5,0a15.5,15.5 0,1 1,31 0a15.5,15.5 0,1 1,-31 0"
+      android:fillColor="#BCEDDF"
+      android:fillAlpha="0.5"/>
+  <path
+      android:pathData="M45.279,108.52L46.48,109.72C47.182,110.414 47.182,111.543 46.48,112.237L34.717,124H31V120.283L42.763,108.52C43.457,107.827 44.586,107.827 45.279,108.52ZM32.778,122.222L34.032,122.275L42.763,113.535L41.51,112.281L32.778,121.013V122.222Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M79,104L99,104A12,12 0,0 1,111 116L111,116A12,12 0,0 1,99 128L79,128A12,12 0,0 1,67 116L67,116A12,12 0,0 1,79 104z"
+      android:fillColor="#E0DCDC"/>
+  <path
+      android:pathData="M79,104L99,104A12,12 0,0 1,111 116L111,116A12,12 0,0 1,99 128L79,128A12,12 0,0 1,67 116L67,116A12,12 0,0 1,79 104z"
+      android:fillColor="#E7E7E7"/>
+  <path
+      android:pathData="M129,104L149,104A12,12 0,0 1,161 116L161,116A12,12 0,0 1,149 128L129,128A12,12 0,0 1,117 116L117,116A12,12 0,0 1,129 104z"
+      android:fillColor="#E0DCDC"/>
+  <path
+      android:pathData="M129,104L149,104A12,12 0,0 1,161 116L161,116A12,12 0,0 1,149 128L129,128A12,12 0,0 1,117 116L117,116A12,12 0,0 1,129 104z"
+      android:fillColor="#E7E7E7"/>
+  <path
+      android:pathData="M22,14L76.49,14A6,6 0,0 1,82.49 20L82.49,38.782A6,6 0,0 1,76.49 44.782L22,44.782A6,6 0,0 1,16 38.782L16,20A6,6 0,0 1,22 14z"
+      android:fillColor="#EDEDED"/>
+  <path
+      android:pathData="M22,56L76.49,56A6,6 0,0 1,82.49 62L82.49,80.782A6,6 0,0 1,76.49 86.782L22,86.782A6,6 0,0 1,16 80.782L16,62A6,6 0,0 1,22 56z"
+      android:fillColor="#EDEDED"/>
+  <path
+      android:pathData="M101,14L155.49,14A6,6 0,0 1,161.49 20L161.49,38.782A6,6 0,0 1,155.49 44.782L101,44.782A6,6 0,0 1,95 38.782L95,20A6,6 0,0 1,101 14z"
+      android:fillColor="#EDEDED"/>
+  <path
+      android:pathData="M101,56L155.49,56A6,6 0,0 1,161.49 62L161.49,80.782A6,6 0,0 1,155.49 86.782L101,86.782A6,6 0,0 1,95 80.782L95,62A6,6 0,0 1,101 56z"
+      android:fillColor="#EDEDED"/>
+</vector>
diff --git a/res/layout/dream_preference_layout.xml b/res/layout/dream_preference_layout.xml
index abb2a34..894ac32 100644
--- a/res/layout/dream_preference_layout.xml
+++ b/res/layout/dream_preference_layout.xml
@@ -64,36 +64,38 @@
             app:layout_constraintStart_toStartOf="@+id/preview"
             app:layout_constraintEnd_toEndOf="@+id/preview"/>
 
-        <ImageView
-            android:id="@+id/icon"
-            android:layout_width="@dimen/dream_item_icon_size"
-            android:layout_height="0dp"
-            android:layout_marginTop="@dimen/dream_item_title_margin_top"
-            android:layout_marginStart="@dimen/dream_item_icon_margin_start"
-            android:layout_marginBottom="@dimen/dream_item_title_margin_bottom"
-            android:gravity="center_vertical"
-            android:importantForAccessibility="no"
-            app:layout_constraintDimensionRatio="1:1"
-            app:layout_constraintHorizontal_chainStyle="packed"
-            app:layout_constraintTop_toBottomOf="@+id/preview"
-            app_layout_constraintEnd_toStartOf="@+id/title_text"
-            app:layout_constraintStart_toStartOf="parent"/>
-
         <TextView
             android:id="@+id/title_text"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/dream_item_title_margin_top"
-            android:layout_marginStart="@dimen/dream_item_title_margin_start"
+            android:layout_marginHorizontal="@dimen/dream_item_title_margin_horizontal"
             android:layout_marginBottom="@dimen/dream_item_title_margin_bottom"
             android:gravity="center_vertical"
             android:maxLines="1"
             android:ellipsize="end"
-            android:textSize="16sp"
+            android:textSize="@dimen/dream_item_title_text_size"
             android:textColor="@color/dream_card_text_color_state_list"
+            android:drawablePadding="@dimen/dream_item_icon_padding"
             app:layout_constraintTop_toBottomOf="@+id/preview"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@+id/icon"/>
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/summary_text"/>
+
+        <TextView
+            android:id="@+id/summary_text"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/dream_item_title_margin_horizontal"
+            android:layout_marginBottom="@dimen/dream_item_title_margin_bottom"
+            android:maxLines="2"
+            android:ellipsize="end"
+            android:textSize="@dimen/dream_item_summary_text_size"
+            android:textColor="@color/dream_card_text_color_state_list"
+            app:layout_constraintTop_toBottomOf="@+id/title_text"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"/>
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 </androidx.cardview.widget.CardView>
diff --git a/res/layout/settings_homepage_container.xml b/res/layout/settings_homepage_container.xml
index 4f0d804..f0aafe1 100644
--- a/res/layout/settings_homepage_container.xml
+++ b/res/layout/settings_homepage_container.xml
@@ -19,7 +19,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/settings_homepage_container"
-    android:fitsSystemWindows="true"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
diff --git a/res/values/config.xml b/res/values/config.xml
index 50eddb5..bf78fd7 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -570,7 +570,19 @@
         <item>2</item>
         <item>3</item>
     </integer-array>
-    
+
+    <!-- The settings/preference description for each settable device state defined in the array
+         "config_perDeviceStateRotationLockDefaults".
+         The item in position "i" describes the auto-rotation setting for the device state also in
+         position "i" in the array "config_perDeviceStateRotationLockDefaults". -->
+    <string-array name="config_settableAutoRotationDeviceStatesDescriptions">
+        <!-- Example:
+            <item>Auto-rotate when folded</item>
+            <item>@null</item> No description for state in position 1
+            <item>Auto-rotate when unfolded</item>
+        -->
+    </string-array>
+
     <!-- Whether to aggregate for network selection list-->
     <bool name="config_network_selection_list_aggregation_enabled">false</bool>
 
@@ -596,4 +608,5 @@
 
     <!-- Whether the dream setup activity should be enabled as part of setupwizard -->
     <bool name="dream_setup_supported">false</bool>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 78dbcce..5cd1ebb 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -445,10 +445,12 @@
     <dimen name="dream_item_corner_radius">28dp</dimen>
     <dimen name="dream_item_content_padding">8dp</dimen>
     <dimen name="dream_item_icon_size">20dp</dimen>
-    <dimen name="dream_item_title_margin_top">18dp</dimen>
+    <dimen name="dream_item_summary_text_size">14sp</dimen>
+    <dimen name="dream_item_title_margin_top">16dp</dimen>
     <dimen name="dream_item_title_margin_bottom">8dp</dimen>
-    <dimen name="dream_item_title_margin_start">18dp</dimen>
-    <dimen name="dream_item_icon_margin_start">10dp</dimen>
+    <dimen name="dream_item_title_text_size">16sp</dimen>
+    <dimen name="dream_item_icon_padding">18dp</dimen>
+    <dimen name="dream_item_title_margin_horizontal">8dp</dimen>
     <dimen name="dream_preference_card_padding">16dp</dimen>
     <dimen name="dream_preference_margin_bottom">20dp</dimen>
     <dimen name="dream_picker_margin_horizontal">48dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d868cb..7726b40 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3186,7 +3186,7 @@
     <!-- [CHAR LIMIT=30] Sound & display settings screen, setting option name to change font size -->
     <string name="title_font_size">Font size</string>
     <!-- Summary for Font size. Lets the user know that this will make text larger or smaller. Appears in the accessibility portion of setup wizard. [CHAR LIMIT=NONE] -->
-    <string name="short_summary_font_size">Make text larger or smaller</string>
+    <string name="short_summary_font_size">Make text bigger or smaller</string>
     <!-- Sound & display settings screen, setting option summary displaying the currently selected font size -->
     <string name="summary_font_size" translatable="false">%1$s</string>
 
@@ -10950,7 +10950,7 @@
     <!-- Title of setting that controls screen zoom (e.g. how large interface elements appear). [CHAR LIMIT=40] -->
     <string name="screen_zoom_title">Display size</string>
     <!-- Short summary for Magnification gesture. Lets the user know that this makes items on screen larger or smaller. Appears in the accessibility portion of Setup Wizard. [CHAR LIMIT=NONE] -->
-    <string name="screen_zoom_short_summary">Make items on screen larger or smaller</string>
+    <string name="screen_zoom_short_summary">Make everything bigger or smaller</string>
     <!-- Keywords for setting that controls screen zoom (e.g. how large interface elements appear). [CHAR LIMIT=NONE] -->
     <string name="screen_zoom_keywords">display density, screen zoom, scale, scaling</string>
     <!-- Summary of screen zoom setting screen. [CHAR LIMIT=NONE] -->
@@ -13731,18 +13731,26 @@
     translate "maximum balance when device is fully charged" instead. Balance is the same meaning as
     having money in a bank account. Balance in our feature is the amount of Android Resource Credits
     an app can have. Android Resource Credits are a form of payment used by apps to be able to
-    perform tasks. [CHAR LIMIT=40]-->
+    perform tasks. [CHAR LIMIT=80]-->
     <string name="tare_max_satiated_balance">Maximum Satiated Balance</string>
-    <!-- Title for the TARE policy factor that determines the maximum credits in circulation between
-    all the apps [CHAR LIMIT=40]-->
-    <string name="tare_max_circulation">Maximum Circulation</string>
-    <!-- Title for the TARE policy factor that determines the minimum credits an app can have in one
-    battery life cycle. Satiated means battery is fully charged; If this is not easily translatable,
-    translate "minimum balance when device is fully charged" instead. Balance is the same meaning as
-    having money in a bank account. Balance in our feature is the amount of Android Resource Credits
-    an app can have. Android Resource Credits are a form of payment used by apps to be able to
-    perform tasks. [CHAR LIMIT=40]-->
-    <string name="tare_min_satiated_balance">Minimum Satiated Balance</string>
+    <!-- Title for the TARE policy factors that affect how many credits an app may have. Balance
+    in this context is the same as "bank balance" or "account balance" (ie. how much "money" may be
+    in a bank account). [CHAR LIMIT=55]-->
+    <string name="tare_balances">Balances</string>
+    <!-- Title for the TARE section to modify consumption limits. "Consumption" refers to the idea
+     using resources that are not replenished. [CHAR LIMIT=55]-->
+    <string name="tare_consumption_limits">Consumption Limits</string>
+    <!-- Title for the TARE policy factor that determines the initial maximum amount of credits that
+    can be consumed by all the apps [CHAR LIMIT=80]-->
+    <string name="tare_initial_consumption_limit">Initial Consumption Limit</string>
+    <!-- Title for the TARE policy factor that determines the maximum consumption limit the system
+     can have [CHAR LIMIT=80]-->
+    <string name="tare_hard_consumption_limit">Maximum Consumption Limit</string>
+    <!-- Titles for the consumption limits factors. [CHAR LIMIT=40]-->
+    <string-array name="tare_consumption_limit_subfactors" translatable="false">
+        <item>@string/tare_initial_consumption_limit</item>
+        <item>@string/tare_hard_consumption_limit</item>
+    </string-array>
     <!-- Title for the various modifiers that alter the cost of TARE tasks based on battery status
     (charging, power save mode, etc.) [CHAR LIMIT=40]-->
     <string name="tare_modifiers">Modifiers</string>
@@ -13778,14 +13786,6 @@
     <string name="tare_nonwakeup_inexact" translatable="false">Inexact NonWakeup Alarm</string>
     <!-- Title for the AlarmClock alarm set via AlarmManager.setAlarmClock() [CHAR LIMIT=50]-->
     <string name="tare_alarm_clock" translatable="false">AlarmClock</string>
-    <!-- Exempted apps are those apps exempted from most power saving features. [CHAR LIMIT=50]-->
-    <string name="tare_exempted">Exempted</string>
-    <!-- A headless system app is a preinstalled app that does not have any activities/UI that the
-    user can interact with. [CHAR LIMIT=50]-->
-    <string name="tare_headless_app">Headless System App</string>
-    <!-- Other apps are those apps interacted with by users that are not exempted or headless
-    system apps. [CHAR LIMIT=50]-->
-    <string name="tare_other_app">Other App</string>
     <!-- Top activity means an app is in the TOP android process state and is thus visible to the
     user[CHAR LIMIT=50]-->
     <string name="tare_top_activity">Top Activity</string>
@@ -13842,12 +13842,29 @@
     <!-- Title for the penalty an app receives for letting a job use the maximum execution time and
      time out [CHAR LIMIT=50]-->
     <string name="tare_job_timeout_penalty">Job Timeout Penalty</string>
+    <!-- Title for the TARE factor that determines the minimum credits an app should have when the
+    device is fully charged. "Satiated" in this context means that the battery is fully charged.
+    Balance is the same meaning as having money in a bank account.
+    Exempted apps are those apps exempted from most power saving features. [CHAR LIMIT=80]-->
+    <string name="tare_min_balance_exempted">Minimum Satiated Balance (Exempted)</string>
+    <!-- Title for the TARE factor that determines the minimum credits an app should have when the
+    device is fully charged. "Satiated" in this context means that the battery is fully charged.
+    Balance is the same meaning as having money in a bank account.
+    A headless system app is a preinstalled app that does not have any activities/UI that the
+    user can interact with. [CHAR LIMIT=80]-->
+    <string name="tare_min_balance_headless_app">Minimum Satiated Balance (Headless System App)</string>
+    <!-- Title for the TARE factor that determines the minimum credits an app should have when the
+    device is fully charged. "Satiated" in this context means that the battery is fully charged.
+    Balance is the same meaning as having money in a bank account.
+    Remaining apps are those apps that don't fit into predefined categories. [CHAR LIMIT=80]-->
+    <string name="tare_min_balance_other_app">Minimum Satiated Balance (Remaining Apps)</string>
     <!-- Titles for the minimum satiated credit balances for different types of apps
     (per battery cycle). Satiated means battery is fully charged. [CHAR LIMIT=40]-->
-    <string-array name="tare_min_satiated_balance_subfactors" translatable="false">
-        <item>@string/tare_exempted</item>
-        <item>@string/tare_headless_app</item>
-        <item>@string/tare_other_app</item>
+    <string-array name="tare_app_balance_subfactors" translatable="false">
+        <item>@string/tare_max_satiated_balance</item>
+        <item>@string/tare_min_balance_exempted</item>
+        <item>@string/tare_min_balance_headless_app</item>
+        <item>@string/tare_min_balance_other_app</item>
     </string-array>
     <!-- Various modifier subfactors that alter the cost of TARE tasks depending on what battery
     state the device is in [CHAR LIMIT=50]-->
diff --git a/res/xml/accessibility_system_controls.xml b/res/xml/accessibility_system_controls.xml
index 71e1143..37c4d67 100644
--- a/res/xml/accessibility_system_controls.xml
+++ b/res/xml/accessibility_system_controls.xml
@@ -42,9 +42,22 @@
         android:title="@string/accessibility_power_button_ends_call_prerefence_title"
         settings:controller="com.android.settings.accessibility.PowerButtonEndsCallPreferenceController"/>
 
+    <!-- Standard auto-rotation preference that will be shown when device state based auto-rotation
+         settings are NOT available. -->
     <SwitchPreference
         android:key="toggle_lock_screen_rotation_preference"
         android:persistent="false"
         android:title="@string/accelerometer_title"
         settings:controller="com.android.settings.accessibility.LockScreenRotationPreferenceController"/>
+
+    <!-- Auto-rotation preference that will be shown when device state based auto-rotation settings
+         are available. -->
+    <Preference
+        android:key="device_state_auto_rotate_accessibility"
+        android:persistent="false"
+        android:title="@string/accelerometer_title"
+        android:fragment="com.android.settings.display.DeviceStateAutoRotateDetailsFragment"
+        settings:keywords="@string/keywords_auto_rotate"
+        settings:controller="com.android.settings.display.DeviceStateAutoRotateOverviewController"/>
+
 </PreferenceScreen>
diff --git a/res/xml/device_state_auto_rotate_settings.xml b/res/xml/device_state_auto_rotate_settings.xml
new file mode 100644
index 0000000..2ddb4c7
--- /dev/null
+++ b/res/xml/device_state_auto_rotate_settings.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/accelerometer_title" >
+
+    <!-- Device state based auto-rotation preferences will be added programmatically here.  -->
+
+</PreferenceScreen>
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 0724eea..ba52a30 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -96,12 +96,27 @@
     <PreferenceCategory
         android:title="@string/category_name_display_controls">
 
+        <!--
+            Standard auto-rotation preference that will be shown when device state based
+            auto-rotation settings are NOT available.
+          -->
         <SwitchPreference
             android:key="auto_rotate"
             android:title="@string/accelerometer_title"
             settings:keywords="@string/keywords_auto_rotate"
             settings:controller="com.android.settings.display.AutoRotatePreferenceController"/>
 
+        <!--
+            Auto-rotation preference that will be shown when device state based auto-rotation
+            settings are available.
+          -->
+        <Preference
+            android:key="device_state_auto_rotate"
+            android:title="@string/accelerometer_title"
+            android:fragment="com.android.settings.display.DeviceStateAutoRotateDetailsFragment"
+            settings:keywords="@string/keywords_auto_rotate"
+            settings:controller="com.android.settings.display.DeviceStateAutoRotateOverviewController"/>
+
         <SwitchPreference
             android:key="display_white_balance"
             android:title="@string/display_white_balance_title"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index e276484..57d7d10 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -33,7 +33,7 @@
 import com.android.settings.network.SubscriptionUtil;
 import com.android.settings.network.telephony.MobileNetworkUtils;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.security.SecuritySettingsFeatureProvider;
 
 import com.google.android.setupdesign.util.ThemeHelper;
@@ -152,7 +152,7 @@
         /** Redirects to SafetyCenter if enabled. */
         @VisibleForTesting
         public void handleSafetyCenterRedirection() {
-            if (SafetyCenterStatusHolder.get().isEnabled(this)) {
+            if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
                 try {
                     startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
                     finish();
@@ -213,7 +213,7 @@
         /** Redirects to SafetyCenter if enabled. */
         @VisibleForTesting
         public void handleSafetyCenterRedirection() {
-            if (SafetyCenterStatusHolder.get().isEnabled(this)) {
+            if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
                 try {
                     startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
                     finish();
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index e485d1e..ae24168 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -34,6 +34,9 @@
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
 import android.net.EthernetManager;
+import android.net.EthernetManager.InterfaceState;
+import android.net.EthernetManager.Role;
+import android.net.IpConfiguration;
 import android.net.TetheringManager;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
@@ -42,10 +45,10 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.SearchIndexableResource;
-import android.text.TextUtils;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.SwitchPreference;
@@ -62,6 +65,7 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -97,7 +101,6 @@
     private BroadcastReceiver mTetherChangeReceiver;
 
     private String[] mBluetoothRegexs;
-    private String mEthernetRegex;
     private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>();
 
     private Handler mHandler = new Handler();
@@ -106,6 +109,7 @@
     private EthernetManager mEm;
     private TetheringEventCallback mTetheringEventCallback;
     private EthernetListener mEthernetListener;
+    private final HashSet<String> mAvailableInterfaces = new HashSet<>();
 
     private WifiTetherPreferenceController mWifiTetherPreferenceController;
 
@@ -172,17 +176,17 @@
         mDataSaverBackend.addListener(this);
 
         mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-        mEm = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE);
         mTm = (TetheringManager) getSystemService(Context.TETHERING_SERVICE);
+        // Some devices do not have available EthernetManager. In that case getSystemService will
+        // return null.
+        mEm = mContext.getSystemService(EthernetManager.class);
 
         mUsbRegexs = mTm.getTetherableUsbRegexs();
         mBluetoothRegexs = mTm.getTetherableBluetoothRegexs();
-        mEthernetRegex = mContext.getResources().getString(
-                com.android.internal.R.string.config_ethernet_iface_regex);
 
         final boolean usbAvailable = mUsbRegexs.length != 0;
         final boolean bluetoothAvailable = adapter != null && mBluetoothRegexs.length != 0;
-        final boolean ethernetAvailable = !TextUtils.isEmpty(mEthernetRegex);
+        final boolean ethernetAvailable = (mEm != null);
 
         if (!usbAvailable || Utils.isMonkeyRunning()) {
             getPreferenceScreen().removePreference(mUsbTether);
@@ -330,7 +334,7 @@
 
         mEthernetListener = new EthernetListener();
         if (mEm != null)
-            mEm.addListener(mEthernetListener, r -> mHandler.post(r));
+            mEm.addInterfaceStateListener(r -> mHandler.post(r), mEthernetListener);
 
         updateUsbState();
         updateBluetoothAndEthernetState();
@@ -346,11 +350,10 @@
         getActivity().unregisterReceiver(mTetherChangeReceiver);
         mTm.unregisterTetheringEventCallback(mTetheringEventCallback);
         if (mEm != null)
-            mEm.removeListener(mEthernetListener);
+            mEm.removeInterfaceStateListener(mEthernetListener);
         mTetherChangeReceiver = null;
         mStartTetheringCallback = null;
         mTetheringEventCallback = null;
-        mEthernetListener = null;
     }
 
     @VisibleForTesting
@@ -483,11 +486,11 @@
         boolean isTethered = false;
 
         for (String s : available) {
-            if (s.matches(mEthernetRegex)) isAvailable = true;
+            if (mAvailableInterfaces.contains(s)) isAvailable = true;
         }
 
         for (String s : tethered) {
-            if (s.matches(mEthernetRegex)) isTethered = true;
+            if (mAvailableInterfaces.contains(s)) isTethered = true;
         }
 
         if (DEBUG) {
@@ -498,7 +501,7 @@
         if (isTethered) {
             mEthernetTether.setEnabled(!mDataSaverEnabled);
             mEthernetTether.setChecked(true);
-        } else if (isAvailable || (mEm != null && mEm.isAvailable())) {
+        } else if (mAvailableInterfaces.size() > 0) {
             mEthernetTether.setEnabled(!mDataSaverEnabled);
             mEthernetTether.setChecked(false);
         } else {
@@ -600,9 +603,9 @@
                         keys.add(KEY_ENABLE_BLUETOOTH_TETHERING);
                     }
 
-                    final boolean ethernetAvailable = !TextUtils.isEmpty(
-                            context.getResources().getString(
-                                    com.android.internal.R.string.config_ethernet_iface_regex));
+                    final EthernetManager em =
+                            context.getSystemService(EthernetManager.class);
+                    final boolean ethernetAvailable = (em != null);
                     if (!ethernetAvailable) {
                         keys.add(KEY_ENABLE_ETHERNET_TETHERING);
                     }
@@ -646,9 +649,15 @@
         }
     }
 
-    private final class EthernetListener implements EthernetManager.Listener {
-        public void onAvailabilityChanged(String iface, boolean isAvailable) {
-            mHandler.post(() -> updateBluetoothAndEthernetState());
+    private final class EthernetListener implements EthernetManager.InterfaceStateListener {
+        public void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
+                @Role int role, @NonNull IpConfiguration configuration) {
+            if (state == EthernetManager.STATE_LINK_UP) {
+                mAvailableInterfaces.add(iface);
+            } else {
+                mAvailableInterfaces.remove(iface);
+            }
+            updateBluetoothAndEthernetState();
         }
     }
 }
diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
index 5798723..59618d6 100644
--- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
@@ -509,7 +509,9 @@
                 ? R.string.accessibility_service_qs_tooltips_content
                 : R.string.accessibility_service_auto_added_qs_tooltips_content;
         final String title = getString(titleResId, tileName);
-        final int imageResId = R.drawable.accessibility_qs_tooltips_illustration;
+        final int imageResId = mNeedsQSTooltipType == QuickSettingsTooltipType.GUIDE_TO_EDIT
+                ? R.drawable.accessibility_qs_tooltips_illustration
+                : R.drawable.accessibility_auto_added_qs_tooltips_illustration;
         mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(getContext());
         mTooltipWindow.setup(title, imageResId);
         mTooltipWindow.showAtTopCenter(getView());
diff --git a/src/com/android/settings/accessibility/LockScreenRotationPreferenceController.java b/src/com/android/settings/accessibility/LockScreenRotationPreferenceController.java
index 5ed4b5c..a1c292a 100644
--- a/src/com/android/settings/accessibility/LockScreenRotationPreferenceController.java
+++ b/src/com/android/settings/accessibility/LockScreenRotationPreferenceController.java
@@ -25,6 +25,7 @@
 import com.android.internal.view.RotationPolicy.RotationPolicyListener;
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.display.DeviceStateAutoRotationHelper;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -59,7 +60,9 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return RotationPolicy.isRotationSupported(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+        return RotationPolicy.isRotationSupported(mContext)
+                && !DeviceStateAutoRotationHelper.isDeviceStateRotationEnabledForA11y(mContext)
+                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 5992ceb..69b8f75 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -79,6 +79,7 @@
 public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment
         implements ShortcutPreference.OnClickCallback, OnMainSwitchChangeListener {
 
+    protected TopIntroPreference mTopIntroPreference;
     protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
     protected ShortcutPreference mShortcutPreference;
     protected Preference mSettingsPreference;
@@ -483,10 +484,10 @@
         if (TextUtils.isEmpty(mTopIntroTitle)) {
             return;
         }
-        final TopIntroPreference topIntroPreference = new TopIntroPreference(getPrefContext());
-        topIntroPreference.setKey(KEY_TOP_INTRO_PREFERENCE);
-        topIntroPreference.setTitle(mTopIntroTitle);
-        getPreferenceScreen().addPreference(topIntroPreference);
+        mTopIntroPreference = new TopIntroPreference(getPrefContext());
+        mTopIntroPreference.setKey(KEY_TOP_INTRO_PREFERENCE);
+        mTopIntroPreference.setTitle(mTopIntroTitle);
+        getPreferenceScreen().addPreference(mTopIntroPreference);
     }
 
     private void initToggleServiceSwitchPreference() {
@@ -879,7 +880,9 @@
                 ? R.string.accessibility_service_qs_tooltips_content
                 : R.string.accessibility_service_auto_added_qs_tooltips_content;
         final String title = getString(titleResId, tileName);
-        final int imageResId = R.drawable.accessibility_qs_tooltips_illustration;
+        final int imageResId = mNeedsQSTooltipType == QuickSettingsTooltipType.GUIDE_TO_EDIT
+                ? R.drawable.accessibility_qs_tooltips_illustration
+                : R.drawable.accessibility_auto_added_qs_tooltips_illustration;
         mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(getContext());
         mTooltipWindow.setup(title, imageResId);
         mTooltipWindow.showAtTopCenter(getView());
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
index d92fd51..cb5ca75 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java
@@ -40,7 +40,7 @@
         final String title = getContext().getString(
                 R.string.accessibility_screen_magnification_title);
         final String description = getContext().getString(
-                R.string.accessibility_preference_magnification_summary);
+                R.string.accessibility_screen_magnification_intro_text);
         final Drawable icon = getContext().getDrawable(R.drawable.ic_accessibility_visibility);
         AccessibilitySetupWizardUtils.updateGlifPreferenceLayout(getContext(), layout, title,
                 description, icon);
@@ -51,6 +51,8 @@
      * Hide the magnification preference settings in the SuW's vision settings.
      */
     private void hidePreferenceSettingComponents() {
+        // Intro
+        mTopIntroPreference.setVisible(false);
         // Setting of magnification type
         mSettingsPreference.setVisible(false);
         // Setting of following typing
diff --git a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
index 7adddf9..f9a1113 100644
--- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
@@ -46,6 +46,9 @@
                 description, icon);
 
         mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
+        if (mTopIntroPreference != null) {
+            mTopIntroPreference.setVisible(false);
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
index b5c1123..a460419 100644
--- a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
@@ -46,6 +46,9 @@
                 description, icon);
 
         mToggleSwitchWasInitiallyChecked = mToggleServiceSwitchPreference.isChecked();
+        if (mTopIntroPreference != null) {
+            mTopIntroPreference.setVisible(false);
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceController.java
index c56f630..8e2d3d4 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceController.java
@@ -25,6 +25,7 @@
 import com.android.settings.applications.AppStateBaseBridge;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.notification.NotificationBackend;
+import com.android.settingslib.applications.AppUtils;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
@@ -128,7 +129,7 @@
             CheckBoxPreference preference = mScreen.findPreference(prefKey);
             if (preference == null) {
                 preference = new CheckBoxPreference(mScreen.getContext());
-                preference.setIcon(entry.icon);
+                preference.setIcon(AppUtils.getIcon(mContext, entry));
                 preference.setTitle(entry.label);
                 preference.setKey(prefKey);
                 mScreen.addPreference(preference);
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
index 8460879..2c52a8c 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
@@ -97,17 +97,13 @@
 
     public static void setAccess(final Context context, final String pkg, final boolean access) {
         logSpecialPermissionChange(access, pkg, context);
-        AsyncTask.execute(() -> {
-            final NotificationManager mgr = context.getSystemService(NotificationManager.class);
-            mgr.setNotificationPolicyAccessGranted(pkg, access);
-        });
+        final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+        mgr.setNotificationPolicyAccessGranted(pkg, access);
     }
 
     public static void deleteRules(final Context context, final String pkg) {
-        AsyncTask.execute(() -> {
-            final NotificationManager mgr = context.getSystemService(NotificationManager.class);
-            mgr.removeAutomaticZenRules(pkg);
-        });
+       final NotificationManager mgr = context.getSystemService(NotificationManager.class);
+       mgr.removeAutomaticZenRules(pkg);
     }
 
     @VisibleForTesting
diff --git a/src/com/android/settings/biometrics/BiometricNavigationUtils.java b/src/com/android/settings/biometrics/BiometricNavigationUtils.java
new file mode 100644
index 0000000..e4f2b7f
--- /dev/null
+++ b/src/com/android/settings/biometrics/BiometricNavigationUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
+import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settings.Utils;
+import com.android.settings.core.SettingsBaseActivity;
+import com.android.settingslib.transition.SettingsTransitionHelper;
+
+/**
+ * Utilities for navigation shared between Security Settings and Safety Center.
+ */
+public class BiometricNavigationUtils {
+
+    private final int mUserId = UserHandle.myUserId();
+
+    /**
+     * Tries to launch the Settings screen if Quiet Mode is not enabled
+     * for managed profile, otherwise shows a dialog to disable the Quiet Mode.
+     *
+     * @param className The class name of Settings screen to launch.
+     * @param extras Extras to put into the launching {@link Intent}.
+     * @return true if the Settings screen is launching.
+     */
+    public boolean launchBiometricSettings(Context context, String className, Bundle extras) {
+        final UserManager userManager = UserManager.get(context);
+        if (Utils.startQuietModeDialogIfNecessary(context, userManager, mUserId)) {
+            return false;
+        }
+
+        final Intent intent = new Intent();
+        intent.setClassName(SETTINGS_PACKAGE_NAME, className);
+        if (!extras.isEmpty()) {
+            intent.putExtras(extras);
+        }
+        intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
+        intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
+        intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
+                SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
+        context.startActivity(intent);
+        return true;
+    }
+}
diff --git a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
index 801dd03..617529f 100644
--- a/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/BiometricStatusPreferenceController.java
@@ -16,11 +16,7 @@
 
 package com.android.settings.biometrics;
 
-import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
-import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY;
-
 import android.content.Context;
-import android.content.Intent;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -30,9 +26,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.Utils;
 import com.android.settings.core.BasePreferenceController;
-import com.android.settings.core.SettingsBaseActivity;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.transition.SettingsTransitionHelper;
 
 public abstract class BiometricStatusPreferenceController extends BasePreferenceController {
 
@@ -42,36 +36,23 @@
     private final int mUserId = UserHandle.myUserId();
     protected final int mProfileChallengeUserId;
 
+    private final BiometricNavigationUtils mBiometricNavigationUtils;
+
     /**
      * @return true if the manager is not null and the hardware is detected.
      */
     protected abstract boolean isDeviceSupported();
 
     /**
-     * @return true if the user has enrolled biometrics of the subclassed type.
+     * @return the summary text.
      */
-    protected abstract boolean hasEnrolledBiometrics();
-
-    /**
-     * @return the summary text if biometrics are enrolled.
-     */
-    protected abstract String getSummaryTextEnrolled();
-
-    /**
-     * @return the summary text if no biometrics are enrolled.
-     */
-    protected abstract String getSummaryTextNoneEnrolled();
+    protected abstract String getSummaryText();
 
     /**
      * @return the class name for the settings page.
      */
     protected abstract String getSettingsClassName();
 
-    /**
-     * @return the class name for entry to enrollment.
-     */
-    protected abstract String getEnrollClassName();
-
     public BiometricStatusPreferenceController(Context context, String key) {
         super(context, key);
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -79,6 +60,7 @@
                 .getSecurityFeatureProvider()
                 .getLockPatternUtils(context);
         mProfileChallengeUserId = Utils.getManagedProfileId(mUm, mUserId);
+        mBiometricNavigationUtils = new BiometricNavigationUtils();
     }
 
     @Override
@@ -103,8 +85,7 @@
         } else {
             preference.setVisible(true);
         }
-        preference.setSummary(hasEnrolledBiometrics() ? getSummaryTextEnrolled()
-                : getSummaryTextNoneEnrolled());
+        preference.setSummary(getSummaryText());
     }
 
     @Override
@@ -113,26 +94,8 @@
             return super.handlePreferenceTreeClick(preference);
         }
 
-        final Context context = preference.getContext();
-        final UserManager userManager = UserManager.get(context);
-        final int userId = getUserId();
-        if (Utils.startQuietModeDialogIfNecessary(context, userManager, userId)) {
-            return false;
-        }
-
-        final Intent intent = new Intent();
-        final String clazz = hasEnrolledBiometrics() ? getSettingsClassName()
-                : getEnrollClassName();
-        intent.setClassName(SETTINGS_PACKAGE_NAME, clazz);
-        if (!preference.getExtras().isEmpty()) {
-            intent.putExtras(preference.getExtras());
-        }
-        intent.putExtra(Intent.EXTRA_USER_ID, userId);
-        intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
-        intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
-                SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
-        context.startActivity(intent);
-        return true;
+        return mBiometricNavigationUtils.launchBiometricSettings(
+                preference.getContext(), getSettingsClassName(), preference.getExtras());
     }
 
     protected int getUserId() {
diff --git a/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java
index 3ac14a1..800139c 100644
--- a/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/combination/BiometricFaceStatusPreferenceController.java
@@ -29,7 +29,7 @@
 public class BiometricFaceStatusPreferenceController extends FaceStatusPreferenceController {
 
     public BiometricFaceStatusPreferenceController(Context context, String key) {
-        super(context, key);
+        super(context, key, null /* lifecycle */);
     }
 
     public BiometricFaceStatusPreferenceController(
diff --git a/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java
index 782d889..be19cb5 100644
--- a/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/combination/BiometricFingerprintStatusPreferenceController.java
@@ -30,7 +30,7 @@
         FingerprintStatusPreferenceController {
 
     public BiometricFingerprintStatusPreferenceController(Context context, String key) {
-        super(context, key);
+        super(context, key, null /* lifecycle */);
     }
 
     public BiometricFingerprintStatusPreferenceController(
diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java
index ddc69e1..b8706a5 100644
--- a/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/combination/CombinedBiometricProfileStatusPreferenceController.java
@@ -20,8 +20,6 @@
 
 import androidx.lifecycle.Lifecycle;
 
-import com.android.settings.Settings;
-
 /**
  * Preference controller for biometrics settings page of work profile, controlling the ability to
  * unlock the phone with face and fingerprint.
@@ -62,11 +60,6 @@
 
     @Override
     protected String getSettingsClassName() {
-        return Settings.CombinedBiometricProfileSettingsActivity.class.getName();
-    }
-
-    @Override
-    protected String getEnrollClassName() {
-        return Settings.CombinedBiometricProfileSettingsActivity.class.getName();
+        return mCombinedBiometricStatusUtils.getProfileSettingsClassName();
     }
 }
diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java
index 32fb3a0..27e3ae7 100644
--- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java
@@ -16,9 +16,6 @@
 package com.android.settings.biometrics.combination;
 
 import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.face.FaceManager;
-import android.hardware.fingerprint.FingerprintManager;
 
 import androidx.annotation.Nullable;
 import androidx.lifecycle.Lifecycle;
@@ -28,11 +25,7 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settings.R;
-import com.android.settings.Settings;
-import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricStatusPreferenceController;
-import com.android.settings.biometrics.ParentalControlsUtils;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedPreference;
 
@@ -44,12 +37,9 @@
         BiometricStatusPreferenceController implements LifecycleObserver {
     private static final String KEY_BIOMETRIC_SETTINGS = "biometric_settings";
 
-    @Nullable
-    FingerprintManager mFingerprintManager;
-    @Nullable
-    FaceManager mFaceManager;
     @VisibleForTesting
     RestrictedPreference mPreference;
+    protected final CombinedBiometricStatusUtils mCombinedBiometricStatusUtils;
 
     public CombinedBiometricStatusPreferenceController(Context context) {
         this(context, KEY_BIOMETRIC_SETTINGS, null /* lifecycle */);
@@ -66,8 +56,7 @@
     public CombinedBiometricStatusPreferenceController(
             Context context, String key, Lifecycle lifecycle) {
         super(context, key);
-        mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
-        mFaceManager = Utils.getFaceManagerOrNull(context);
+        mCombinedBiometricStatusUtils = new CombinedBiometricStatusUtils(context);
 
         if (lifecycle != null) {
             lifecycle.addObserver(this);
@@ -87,12 +76,7 @@
 
     @Override
     protected boolean isDeviceSupported() {
-        return Utils.hasFingerprintHardware(mContext) && Utils.hasFaceHardware(mContext);
-    }
-
-    @Override
-    protected boolean hasEnrolledBiometrics() {
-        return false;
+        return mCombinedBiometricStatusUtils.isAvailable();
     }
 
     @Override
@@ -102,80 +86,35 @@
     }
 
     private void updateStateInternal() {
-        // This controller currently is shown if fingerprint&face exist on the device. If this
-        // changes in the future, the modalities passed into the below will need to be updated.
+        final RestrictedLockUtils.EnforcedAdmin admin =
+                mCombinedBiometricStatusUtils.getDisablingAdmin();
 
-        final RestrictedLockUtils.EnforcedAdmin faceAdmin = ParentalControlsUtils
-                .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FACE);
-        final RestrictedLockUtils.EnforcedAdmin fpAdmin = ParentalControlsUtils
-                .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FINGERPRINT);
-
-        // If the admins are non-null, they are actually always the same. Just the helper class
-        // we create above always return the admin, instead of a boolean.
-        final boolean faceConsentRequired = faceAdmin != null;
-        final boolean fpConsentRequired = fpAdmin != null;
-        final RestrictedLockUtils.EnforcedAdmin admin = faceAdmin != null ? faceAdmin : fpAdmin;
-
-        updateStateInternal(admin, faceConsentRequired, fpConsentRequired);
+        updateStateInternal(admin);
     }
 
+    /**
+     *   Disables the preference and shows the consent flow only if consent is required for all
+     *   modalities.
+     *
+     *   <p>Otherwise, users will not be able to enter and modify settings for modalities which have
+     *   already been consented. In any case, the controllers for the modalities which have not yet
+     *   been consented will be disabled in the combined page anyway - users can go through the
+     *   consent+enrollment flow from there.
+     */
     @VisibleForTesting
-    void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin,
-            boolean faceConsentRequired, boolean fpConsentRequired) {
-        // Disable the preference (and show the consent flow) only if consent is required for all
-        // modalities. Otherwise, users will not be able to enter and modify settings for modalities
-        // which have already been consented. In any case, the controllers for the modalities which
-        // have not yet been consented will be disabled in the combined page anyway - users can
-        // go through the consent+enrollment flow from there.
-        final boolean disablePreference = faceConsentRequired && fpConsentRequired;
-        if (!disablePreference) {
-            enforcedAdmin = null;
-        }
-
+    void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin) {
         if (mPreference != null) {
             mPreference.setDisabledByAdmin(enforcedAdmin);
         }
     }
 
     @Override
-    protected String getSummaryTextEnrolled() {
-        // Note that this is currently never called (see the super class)
-        return mContext.getString(
-                R.string.security_settings_biometric_preference_summary_none_enrolled);
-    }
-
-    @Override
-    protected String getSummaryTextNoneEnrolled() {
-        final int numFingerprintsEnrolled = mFingerprintManager != null ?
-                mFingerprintManager.getEnrolledFingerprints(getUserId()).size() : 0;
-        final boolean faceEnrolled = mFaceManager != null
-                && mFaceManager.hasEnrolledTemplates(getUserId());
-
-        if (faceEnrolled && numFingerprintsEnrolled > 1) {
-            return mContext.getString(
-                    R.string.security_settings_biometric_preference_summary_both_fp_multiple);
-        } else if (faceEnrolled && numFingerprintsEnrolled == 1) {
-            return mContext.getString(
-                    R.string.security_settings_biometric_preference_summary_both_fp_single);
-        } else if (faceEnrolled) {
-            return mContext.getString(R.string.security_settings_face_preference_summary);
-        } else if (numFingerprintsEnrolled > 0) {
-            return mContext.getResources().getQuantityString(
-                    R.plurals.security_settings_fingerprint_preference_summary,
-                    numFingerprintsEnrolled, numFingerprintsEnrolled);
-        } else {
-            return mContext.getString(
-                    R.string.security_settings_biometric_preference_summary_none_enrolled);
-        }
+    protected String getSummaryText() {
+        return mCombinedBiometricStatusUtils.getSummary();
     }
 
     @Override
     protected String getSettingsClassName() {
-        return Settings.CombinedBiometricSettingsActivity.class.getName();
-    }
-
-    @Override
-    protected String getEnrollClassName() {
-        return Settings.CombinedBiometricSettingsActivity.class.getName();
+        return mCombinedBiometricStatusUtils.getSettingsClassName();
     }
 }
diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java
new file mode 100644
index 0000000..a7554c8
--- /dev/null
+++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java
@@ -0,0 +1,122 @@
+/*
+ * 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.combination;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.Utils;
+import com.android.settings.biometrics.ParentalControlsUtils;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+/**
+ * Utilities for combined biometric details shared between Security Settings and Safety Center.
+ */
+public class CombinedBiometricStatusUtils {
+
+    private final int mUserId = UserHandle.myUserId();
+    private final Context mContext;
+    @Nullable
+    FingerprintManager mFingerprintManager;
+    @Nullable
+    FaceManager mFaceManager;
+
+    public CombinedBiometricStatusUtils(Context context) {
+        mContext = context;
+        mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
+        mFaceManager = Utils.getFaceManagerOrNull(context);
+    }
+
+    /**
+     * Returns whether the combined biometric settings entity should be shown.
+     */
+    public boolean isAvailable() {
+        return Utils.hasFingerprintHardware(mContext) && Utils.hasFaceHardware(mContext);
+    }
+
+    /**
+     * Returns the {@link EnforcedAdmin} in case parental consent is required to change both
+     * face and fingerprint settings.
+     *
+     * @return null if either face or fingerprint settings do not require a parental consent.
+     */
+    public EnforcedAdmin getDisablingAdmin() {
+        // This controller currently is shown if fingerprint&face exist on the device. If this
+        // changes in the future, the modalities passed into the below will need to be updated.
+
+        final EnforcedAdmin faceAdmin = ParentalControlsUtils
+                .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FACE);
+        final EnforcedAdmin fpAdmin = ParentalControlsUtils
+                .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FINGERPRINT);
+
+        final boolean faceConsentRequired = faceAdmin != null;
+        final boolean fpConsentRequired = fpAdmin != null;
+
+        // Result is only required if all modalities require consent.
+        // If the admins are non-null, they are actually always the same.
+        return faceConsentRequired && fpConsentRequired ? faceAdmin : null;
+    }
+
+    /**
+     * Returns the summary of combined biometric settings entity.
+     */
+    public String getSummary() {
+        final int numFingerprintsEnrolled = mFingerprintManager != null
+                ? mFingerprintManager.getEnrolledFingerprints(mUserId).size() : 0;
+        final boolean faceEnrolled = mFaceManager != null
+                && mFaceManager.hasEnrolledTemplates(mUserId);
+
+        if (faceEnrolled && numFingerprintsEnrolled > 1) {
+            return mContext.getString(
+                    R.string.security_settings_biometric_preference_summary_both_fp_multiple);
+        } else if (faceEnrolled && numFingerprintsEnrolled == 1) {
+            return mContext.getString(
+                    R.string.security_settings_biometric_preference_summary_both_fp_single);
+        } else if (faceEnrolled) {
+            return mContext.getString(R.string.security_settings_face_preference_summary);
+        } else if (numFingerprintsEnrolled > 0) {
+            return mContext.getResources().getQuantityString(
+                    R.plurals.security_settings_fingerprint_preference_summary,
+                    numFingerprintsEnrolled, numFingerprintsEnrolled);
+        } else {
+            return mContext.getString(
+                    R.string.security_settings_biometric_preference_summary_none_enrolled);
+        }
+    }
+
+    /**
+     * Returns the class name of the Settings page corresponding to combined biometric settings.
+     */
+    public String getSettingsClassName() {
+        return Settings.CombinedBiometricSettingsActivity.class.getName();
+    }
+
+    /**
+     * Returns the class name of the Settings page corresponding to combined biometric settings
+     * for work profile.
+     */
+    public String getProfileSettingsClassName() {
+        return Settings.CombinedBiometricProfileSettingsActivity.class.getName();
+    }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java b/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java
index 319166e..cd0bc15 100644
--- a/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java
@@ -17,7 +17,6 @@
 package com.android.settings.biometrics.face;
 
 import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.face.FaceManager;
 
 import androidx.annotation.Nullable;
@@ -28,11 +27,8 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settings.R;
-import com.android.settings.Settings;
 import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricStatusPreferenceController;
-import com.android.settings.biometrics.ParentalControlsUtils;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedPreference;
 
@@ -44,6 +40,7 @@
     protected final FaceManager mFaceManager;
     @VisibleForTesting
     RestrictedPreference mPreference;
+    private final FaceStatusUtils mFaceStatusUtils;
 
     public FaceStatusPreferenceController(Context context) {
         this(context, KEY_FACE_SETTINGS, null /* lifecycle */);
@@ -60,6 +57,7 @@
     public FaceStatusPreferenceController(Context context, String key, Lifecycle lifecycle) {
         super(context, key);
         mFaceManager = Utils.getFaceManagerOrNull(context);
+        mFaceStatusUtils = new FaceStatusUtils(context, mFaceManager);
 
         if (lifecycle != null) {
             lifecycle.addObserver(this);
@@ -79,12 +77,7 @@
 
     @Override
     protected boolean isDeviceSupported() {
-        return !Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFaceHardware(mContext);
-    }
-
-    @Override
-    protected boolean hasEnrolledBiometrics() {
-        return mFaceManager.hasEnrolledTemplates(getUserId());
+        return mFaceStatusUtils.isAvailable();
     }
 
     @Override
@@ -94,8 +87,7 @@
     }
 
     private void updateStateInternal() {
-        updateStateInternal(ParentalControlsUtils.parentConsentRequired(
-                mContext, BiometricAuthenticator.TYPE_FACE));
+        updateStateInternal(mFaceStatusUtils.getDisablingAdmin());
     }
 
     @VisibleForTesting
@@ -106,25 +98,12 @@
     }
 
     @Override
-    protected String getSummaryTextEnrolled() {
-        return mContext.getResources()
-                .getString(R.string.security_settings_face_preference_summary);
-    }
-
-    @Override
-    protected String getSummaryTextNoneEnrolled() {
-        return mContext.getResources()
-                .getString(R.string.security_settings_face_preference_summary_none);
+    protected String getSummaryText() {
+        return mFaceStatusUtils.getSummary();
     }
 
     @Override
     protected String getSettingsClassName() {
-        return Settings.FaceSettingsActivity.class.getName();
+        return mFaceStatusUtils.getSettingsClassName();
     }
-
-    @Override
-    protected String getEnrollClassName() {
-        return FaceEnrollIntroduction.class.getName();
-    }
-
 }
diff --git a/src/com/android/settings/biometrics/face/FaceStatusUtils.java b/src/com/android/settings/biometrics/face/FaceStatusUtils.java
new file mode 100644
index 0000000..dd32708
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceStatusUtils.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.face.FaceManager;
+import android.os.UserHandle;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.Utils;
+import com.android.settings.biometrics.ParentalControlsUtils;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+/**
+ * Utilities for face details shared between Security Settings and Safety Center.
+ */
+public class FaceStatusUtils {
+
+    private final int mUserId = UserHandle.myUserId();
+    private final Context mContext;
+    private final FaceManager mFaceManager;
+
+    public FaceStatusUtils(Context context, FaceManager faceManager) {
+        mContext = context;
+        mFaceManager = faceManager;
+    }
+
+    /**
+     * Returns whether the face settings entity should be shown.
+     */
+    public boolean isAvailable() {
+        return !Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFaceHardware(mContext);
+    }
+
+    /**
+     * Returns the {@link EnforcedAdmin} if parental consent is required to change face settings.
+     *
+     * @return null if face settings does not require a parental consent.
+     */
+    public EnforcedAdmin getDisablingAdmin() {
+        return ParentalControlsUtils.parentConsentRequired(
+                mContext, BiometricAuthenticator.TYPE_FACE);
+    }
+
+    /**
+     * Returns the summary of face settings entity.
+     */
+    public String getSummary() {
+        return mContext.getResources().getString(hasEnrolled()
+                ? R.string.security_settings_face_preference_summary
+                : R.string.security_settings_face_preference_summary_none);
+    }
+
+    /**
+     * Returns the class name of the Settings page corresponding to face settings.
+     */
+    public String getSettingsClassName() {
+        return hasEnrolled() ? Settings.FaceSettingsActivity.class.getName()
+                : FaceEnrollIntroduction.class.getName();
+    }
+
+    private boolean hasEnrolled() {
+        return mFaceManager.hasEnrolledTemplates(mUserId);
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java
index 76c809c..646af4d 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java
@@ -17,7 +17,6 @@
 package com.android.settings.biometrics.fingerprint;
 
 import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.fingerprint.FingerprintManager;
 
 import androidx.annotation.Nullable;
@@ -28,10 +27,8 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricStatusPreferenceController;
-import com.android.settings.biometrics.ParentalControlsUtils;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedPreference;
 
@@ -43,6 +40,7 @@
     protected final FingerprintManager mFingerprintManager;
     @VisibleForTesting
     RestrictedPreference mPreference;
+    private final FingerprintStatusUtils mFingerprintStatusUtils;
 
     public FingerprintStatusPreferenceController(Context context) {
         this(context, KEY_FINGERPRINT_SETTINGS);
@@ -59,6 +57,8 @@
     public FingerprintStatusPreferenceController(Context context, String key, Lifecycle lifecycle) {
         super(context, key);
         mFingerprintManager = Utils.getFingerprintManagerOrNull(context);
+        mFingerprintStatusUtils =
+                new FingerprintStatusUtils(context, mFingerprintManager);
 
         if (lifecycle != null) {
             lifecycle.addObserver(this);
@@ -78,13 +78,7 @@
 
     @Override
     protected boolean isDeviceSupported() {
-        return !Utils.isMultipleBiometricsSupported(mContext)
-                && Utils.hasFingerprintHardware(mContext);
-    }
-
-    @Override
-    protected boolean hasEnrolledBiometrics() {
-        return mFingerprintManager.hasEnrolledFingerprints(getUserId());
+        return mFingerprintStatusUtils.isAvailable();
     }
 
     @Override
@@ -94,8 +88,17 @@
     }
 
     private void updateStateInternal() {
-        updateStateInternal(ParentalControlsUtils.parentConsentRequired(
-                mContext, BiometricAuthenticator.TYPE_FINGERPRINT));
+        updateStateInternal(mFingerprintStatusUtils.getDisablingAdmin());
+    }
+
+    @Override
+    protected String getSummaryText() {
+        return mFingerprintStatusUtils.getSummary();
+    }
+
+    @Override
+    protected String getSettingsClassName() {
+        return mFingerprintStatusUtils.getSettingsClassName();
     }
 
     @VisibleForTesting
@@ -104,28 +107,4 @@
             mPreference.setDisabledByAdmin(enforcedAdmin);
         }
     }
-
-    @Override
-    protected String getSummaryTextEnrolled() {
-        final int numEnrolled = mFingerprintManager.getEnrolledFingerprints(getUserId()).size();
-        return mContext.getResources().getQuantityString(
-                R.plurals.security_settings_fingerprint_preference_summary,
-                numEnrolled, numEnrolled);
-    }
-
-    @Override
-    protected String getSummaryTextNoneEnrolled() {
-        return mContext.getString(R.string.security_settings_fingerprint_preference_summary_none);
-    }
-
-    @Override
-    protected String getSettingsClassName() {
-        return FingerprintSettings.class.getName();
-    }
-
-    @Override
-    protected String getEnrollClassName() {
-        return FingerprintEnrollIntroduction.class.getName();
-    }
-
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java
new file mode 100644
index 0000000..36edd2e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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.fingerprint;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.biometrics.ParentalControlsUtils;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+/**
+ * Utilities for fingerprint details shared between Security Settings and Safety Center.
+ */
+public class FingerprintStatusUtils {
+
+    private final int mUserId = UserHandle.myUserId();
+    private final Context mContext;
+    private final FingerprintManager mFingerprintManager;
+
+    public FingerprintStatusUtils(Context context, FingerprintManager fingerprintManager) {
+        mContext = context;
+        mFingerprintManager = fingerprintManager;
+    }
+
+    /**
+     * Returns whether the fingerprint settings entity should be shown.
+     */
+    public boolean isAvailable() {
+        return !Utils.isMultipleBiometricsSupported(mContext)
+                && Utils.hasFingerprintHardware(mContext);
+    }
+
+    /**
+     * Returns the {@link EnforcedAdmin} if parental consent is required to change face settings.
+     *
+     * @return null if face settings does not require a parental consent.
+     */
+    public EnforcedAdmin getDisablingAdmin() {
+        return ParentalControlsUtils.parentConsentRequired(
+                mContext, BiometricAuthenticator.TYPE_FINGERPRINT);
+    }
+
+    /**
+     * Returns the summary of fingerprint settings entity.
+     */
+    public String getSummary() {
+        if (hasEnrolled()) {
+            final int numEnrolled = mFingerprintManager.getEnrolledFingerprints(mUserId).size();
+            return mContext.getResources().getQuantityString(
+                    R.plurals.security_settings_fingerprint_preference_summary,
+                    numEnrolled, numEnrolled);
+        } else {
+            return mContext.getString(
+                    R.string.security_settings_fingerprint_preference_summary_none);
+        }
+    }
+
+    /**
+     * Returns the class name of the Settings page corresponding to fingerprint settings.
+     */
+    public String getSettingsClassName() {
+        return hasEnrolled() ? FingerprintSettings.class.getName()
+                : FingerprintEnrollIntroduction.class.getName();
+    }
+
+    private boolean hasEnrolled() {
+        return mFingerprintManager.hasEnrolledFingerprints(mUserId);
+    }
+}
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index b60f1b6..1c12c6a 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -99,6 +99,10 @@
     @VisibleForTesting
     boolean mIsRegisterCallback = false;
     @VisibleForTesting
+    boolean mIsLeftDeviceEstimateReady;
+    @VisibleForTesting
+    boolean mIsRightDeviceEstimateReady;
+    @VisibleForTesting
     final BluetoothAdapter.OnMetadataChangedListener mMetadataListener =
             new BluetoothAdapter.OnMetadataChangedListener() {
                 @Override
@@ -226,6 +230,8 @@
                         BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
                         R.string.bluetooth_right_name,
                         RIGHT_DEVICE_ID);
+
+                showBothDevicesBatteryPredictionIfNecessary();
             }
         }
     }
@@ -365,8 +371,13 @@
                                 + ", ESTIMATE_READY : " + estimateReady
                                 + ", BATTERY_ESTIMATE : " + batteryEstimate);
                     }
-                    showBatteryPredictionIfNecessary(estimateReady, batteryEstimate,
-                            linearLayout);
+
+                    showBatteryPredictionIfNecessary(estimateReady, batteryEstimate, linearLayout);
+                    if (batteryId == LEFT_DEVICE_ID) {
+                        mIsLeftDeviceEstimateReady = estimateReady == 1;
+                    } else if (batteryId == RIGHT_DEVICE_ID) {
+                        mIsRightDeviceEstimateReady = estimateReady == 1;
+                    }
                 }
             } finally {
                 cursor.close();
@@ -380,7 +391,6 @@
         ThreadUtils.postOnMainThread(() -> {
             final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
             if (estimateReady == 1) {
-                textView.setVisibility(View.VISIBLE);
                 textView.setText(
                         StringUtil.formatElapsedTime(
                                 mContext,
@@ -393,6 +403,24 @@
         });
     }
 
+    @VisibleForTesting
+    void showBothDevicesBatteryPredictionIfNecessary() {
+        TextView leftDeviceTextView =
+                mLayoutPreference.findViewById(R.id.layout_left)
+                        .findViewById(R.id.bt_battery_prediction);
+        TextView rightDeviceTextView =
+                mLayoutPreference.findViewById(R.id.layout_right)
+                        .findViewById(R.id.bt_battery_prediction);
+
+        boolean isBothDevicesEstimateReady =
+                mIsLeftDeviceEstimateReady && mIsRightDeviceEstimateReady;
+        int visibility = isBothDevicesEstimateReady ? View.VISIBLE : View.GONE;
+        ThreadUtils.postOnMainThread(() -> {
+            leftDeviceTextView.setVisibility(visibility);
+            rightDeviceTextView.setVisibility(visibility);
+        });
+    }
+
     private void showBatteryIcon(LinearLayout linearLayout, int level, int lowBatteryLevel,
             boolean charging) {
         final boolean enableLowBattery = level <= lowBatteryLevel && !charging;
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index cfdfdaa..8b1d633 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -53,6 +53,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -280,6 +281,11 @@
         return null;
     }
 
+    /** Returns all controllers of type T. */
+    protected <T extends AbstractPreferenceController> List<T> useAll(Class<T> clazz) {
+        return (List<T>) mPreferenceControllers.getOrDefault(clazz, Collections.emptyList());
+    }
+
     protected void addPreferenceController(AbstractPreferenceController controller) {
         if (mPreferenceControllers.get(controller.getClass()) == null) {
             mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
diff --git a/src/com/android/settings/development/tare/AlarmManagerFragment.java b/src/com/android/settings/development/tare/AlarmManagerFragment.java
index dbc4e58..fe76b12 100644
--- a/src/com/android/settings/development/tare/AlarmManagerFragment.java
+++ b/src/com/android/settings/development/tare/AlarmManagerFragment.java
@@ -15,17 +15,16 @@
  */
 package com.android.settings.development.tare;
 
+import android.annotation.Nullable;
 import android.app.Fragment;
+import android.app.tare.EconomyManager;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.OnChildClickListener;
-import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.settings.R;
 
@@ -33,130 +32,96 @@
  * Creates the AlarmManager fragment to display all the AlarmManager factors
  * when the AlarmManager policy is chosen in the dropdown TARE menu.
  */
-public class AlarmManagerFragment extends Fragment {
+public class AlarmManagerFragment extends Fragment implements
+        TareFactorController.DataChangeListener {
+
+    private TareFactorController mFactorController;
+
+    private TareFactorExpandableListAdapter mExpandableListAdapter;
+
+    private String[] mGroups;
+    private String[][] mChildren;
+    private String[][] mKeys;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFactorController = TareFactorController.getInstance(getContext());
+        populateArrays();
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.tare_policy_fragment, null);
         ExpandableListView elv = (ExpandableListView) v.findViewById(R.id.factor_list);
-        final SavedTabsListAdapter expListAdapter = new SavedTabsListAdapter();
+        mExpandableListAdapter = new TareFactorExpandableListAdapter(
+                mFactorController, LayoutInflater.from(getActivity()), mGroups, mChildren, mKeys);
         elv.setGroupIndicator(null);
-        elv.setAdapter(expListAdapter);
+        elv.setAdapter(mExpandableListAdapter);
         elv.setOnChildClickListener(new OnChildClickListener() {
             public boolean onChildClick(ExpandableListView parent, View v,
                     int groupPosition, int childPosition, long id) {
-                final String selected =
-                        (String) expListAdapter.getChild(groupPosition, childPosition);
-                Toast.makeText(getActivity(), selected, Toast.LENGTH_SHORT).show();
+                final String key = mExpandableListAdapter.getKey(groupPosition, childPosition);
+                mFactorController.createDialog(key).show(getFragmentManager(), key);
                 return true;
             }
         });
         return v;
     }
 
-     /**
-     * Creates the expandable list containing all AlarmManager factors within the
-     * AlarmManager fragment.
-     */
-    public class SavedTabsListAdapter extends BaseExpandableListAdapter {
+    @Override
+    public void onStart() {
+        super.onStart();
+        mFactorController.registerListener(this);
+    }
 
-        private final LayoutInflater mInflater;
-        private Resources mResources = getActivity().getResources();
+    @Override
+    public void onStop() {
+        mFactorController.unregisterListener(this);
+        super.onStop();
+    }
 
-        private String[] mGroups = {
-                mResources.getString(R.string.tare_max_circulation),
-                mResources.getString(R.string.tare_max_satiated_balance),
-                mResources.getString(R.string.tare_min_satiated_balance),
-                mResources.getString(R.string.tare_modifiers),
-                mResources.getString(R.string.tare_actions),
-                mResources.getString(R.string.tare_rewards)
+    @Override
+    public void onDataChanged() {
+        mExpandableListAdapter.notifyDataSetChanged();
+    }
+
+    private void populateArrays() {
+        final Resources resources = getResources();
+
+        mGroups = new String[]{
+                resources.getString(R.string.tare_consumption_limits),
+                resources.getString(R.string.tare_balances),
+                // resources.getString(R.string.tare_modifiers),
+                // resources.getString(R.string.tare_actions),
+                // resources.getString(R.string.tare_rewards)
         };
 
-        /*
-         * First two are empty arrays because the first two factors have no subfactors (no
-         * children).
-         */
-        private String[][] mChildren = {
-                {},
-                {},
-                mResources.getStringArray(R.array.tare_min_satiated_balance_subfactors),
-                mResources.getStringArray(R.array.tare_modifiers_subfactors),
-                mResources.getStringArray(R.array.tare_alarm_manager_actions),
-                mResources.getStringArray(R.array.tare_rewards_subfactors)
+        mChildren = new String[][]{
+                resources.getStringArray(R.array.tare_consumption_limit_subfactors),
+                resources.getStringArray(R.array.tare_app_balance_subfactors),
+                // TODO: support
+                // resources.getStringArray(R.array.tare_modifiers_subfactors),
+                // resources.getStringArray(R.array.tare_alarm_manager_actions),
+                // resources.getStringArray(R.array.tare_rewards_subfactors)
         };
 
-        public SavedTabsListAdapter() {
-            mInflater = LayoutInflater.from(getActivity());
-        }
-
-        @Override
-        public int getGroupCount() {
-            return mGroups.length;
-        }
-
-        @Override
-        public int getChildrenCount(int groupPosition) {
-            return mChildren[groupPosition].length;
-        }
-
-        @Override
-        public Object getGroup(int groupPosition) {
-            return mGroups[groupPosition];
-        }
-
-        @Override
-        public Object getChild(int groupPosition, int childPosition) {
-            return mChildren[groupPosition][childPosition];
-        }
-
-        @Override
-        public long getGroupId(int groupPosition) {
-            return groupPosition;
-        }
-
-        @Override
-        public long getChildId(int groupPosition, int childPosition) {
-            return childPosition;
-        }
-
-        @Override
-        public boolean hasStableIds() {
-            return true;
-        }
-
-        @Override
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            if (convertView == null) {
-                convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
-            }
-            TextView factor = (TextView) convertView.findViewById(android.R.id.text1);
-            factor.setText(getGroup(groupPosition).toString());
-            return convertView;
-        }
-
-        @Override
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            // Here a custom child item is used instead of android.R.simple_list_item_2 because it
-            // is more customizable for this specific UI
-            if (convertView == null) {
-                convertView = mInflater.inflate(R.layout.tare_child_item, null);
-            }
-            TextView factor = (TextView) convertView.findViewById(R.id.factor);
-            TextView value = (TextView) convertView.findViewById(R.id.factor_number);
-
-            // TODO: Replace these hardcoded values with either default or user inputted TARE values
-            factor.setText(getChild(groupPosition, childPosition).toString());
-            value.setText("500");
-
-            return convertView;
-        }
-
-        @Override
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
+        mKeys = new String[][]{
+                {
+                        EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT,
+                        EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT
+                },
+                {
+                        EconomyManager.KEY_AM_MAX_SATIATED_BALANCE,
+                        EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
+                        EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+                        EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP
+                },
+                // {},
+                // {},
+                // {},
+        };
     }
 }
diff --git a/src/com/android/settings/development/tare/DropdownActivity.java b/src/com/android/settings/development/tare/DropdownActivity.java
index 55f1fec..66b57dd 100644
--- a/src/com/android/settings/development/tare/DropdownActivity.java
+++ b/src/com/android/settings/development/tare/DropdownActivity.java
@@ -43,7 +43,6 @@
     static final int POLICY_JOB_SCHEDULER = 1;
     private static final int DEFAULT_POLICY = POLICY_ALARM_MANAGER;
 
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/src/com/android/settings/development/tare/JobSchedulerFragment.java b/src/com/android/settings/development/tare/JobSchedulerFragment.java
index 5a7f4a9..1c6598c 100644
--- a/src/com/android/settings/development/tare/JobSchedulerFragment.java
+++ b/src/com/android/settings/development/tare/JobSchedulerFragment.java
@@ -15,17 +15,16 @@
  */
 package com.android.settings.development.tare;
 
+import android.annotation.Nullable;
 import android.app.Fragment;
+import android.app.tare.EconomyManager;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.OnChildClickListener;
-import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.settings.R;
 
@@ -33,131 +32,97 @@
  * Creates the JobScheduler fragment to display all the JobScheduler factors
  * when the JobScheduler policy is chosen in the dropdown TARE menu.
  */
-public class JobSchedulerFragment extends Fragment {
+public class JobSchedulerFragment extends Fragment implements
+        TareFactorController.DataChangeListener {
+
+    private TareFactorController mFactorController;
+
+    private TareFactorExpandableListAdapter mExpandableListAdapter;
+
+    private String[] mGroups;
+    private String[][] mChildren;
+    private String[][] mKeys;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFactorController = TareFactorController.getInstance(getContext());
+        populateArrays();
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.tare_policy_fragment, null);
         ExpandableListView elv = (ExpandableListView) v.findViewById(R.id.factor_list);
-        final SavedTabsListAdapter expListAdapter = new SavedTabsListAdapter();
+        mExpandableListAdapter = new TareFactorExpandableListAdapter(
+                mFactorController, LayoutInflater.from(getActivity()), mGroups, mChildren, mKeys);
         elv.setGroupIndicator(null);
-        elv.setAdapter(expListAdapter);
+        elv.setAdapter(mExpandableListAdapter);
         elv.setOnChildClickListener(new OnChildClickListener() {
             public boolean onChildClick(ExpandableListView parent, View v,
                     int groupPosition, int childPosition, long id) {
-                final String selected =
-                        (String) expListAdapter.getChild(groupPosition, childPosition);
-                Toast.makeText(getActivity(), selected, Toast.LENGTH_SHORT)
-                        .show();
+                final String key = mExpandableListAdapter.getKey(groupPosition, childPosition);
+                mFactorController.createDialog(key).show(getFragmentManager(), key);
                 return true;
             }
         });
+
         return v;
     }
 
-    /**
-     * Creates the expandable list containing all JobScheduler factors within the
-     * JobScheduler fragment.
-     */
-    public class SavedTabsListAdapter extends BaseExpandableListAdapter {
+    @Override
+    public void onStart() {
+        super.onStart();
+        mFactorController.registerListener(this);
+    }
 
-        private final LayoutInflater mInflater;
-        private Resources mResources = getActivity().getResources();
+    @Override
+    public void onStop() {
+        mFactorController.unregisterListener(this);
+        super.onStop();
+    }
 
-        private String[] mGroups = {
-                mResources.getString(R.string.tare_max_circulation),
-                mResources.getString(R.string.tare_max_satiated_balance),
-                mResources.getString(R.string.tare_min_satiated_balance),
-                mResources.getString(R.string.tare_modifiers),
-                mResources.getString(R.string.tare_actions),
-                mResources.getString(R.string.tare_rewards)
+    @Override
+    public void onDataChanged() {
+        mExpandableListAdapter.notifyDataSetChanged();
+    }
+
+    private void populateArrays() {
+        final Resources resources = getResources();
+
+        mGroups = new String[]{
+                resources.getString(R.string.tare_consumption_limits),
+                resources.getString(R.string.tare_balances),
+                // mResources.getString(R.string.tare_modifiers),
+                // mResources.getString(R.string.tare_actions),
+                // mResources.getString(R.string.tare_rewards)
         };
 
-        /*
-         * First two are empty arrays because the first two factors have no subfactors (no
-         * children).
-         */
-        private String[][] mChildren = {
-                {},
-                {},
-                mResources.getStringArray(R.array.tare_min_satiated_balance_subfactors),
-                mResources.getStringArray(R.array.tare_modifiers_subfactors),
-                mResources.getStringArray(R.array.tare_job_scheduler_actions),
-                mResources.getStringArray(R.array.tare_rewards_subfactors)
+        mChildren = new String[][]{
+                resources.getStringArray(R.array.tare_consumption_limit_subfactors),
+                resources.getStringArray(R.array.tare_app_balance_subfactors),
+                // TODO: support
+                // mResources.getStringArray(R.array.tare_modifiers_subfactors),
+                // mResources.getStringArray(R.array.tare_job_scheduler_actions),
+                // mResources.getStringArray(R.array.tare_rewards_subfactors)
         };
 
-        public SavedTabsListAdapter() {
-            mInflater = LayoutInflater.from(getActivity());
-        }
-
-        @Override
-        public int getGroupCount() {
-            return mGroups.length;
-        }
-
-        @Override
-        public int getChildrenCount(int groupPosition) {
-            return mChildren[groupPosition].length;
-        }
-
-        @Override
-        public Object getGroup(int groupPosition) {
-            return mGroups[groupPosition];
-        }
-
-        @Override
-        public Object getChild(int groupPosition, int childPosition) {
-            return mChildren[groupPosition][childPosition];
-        }
-
-        @Override
-        public long getGroupId(int groupPosition) {
-            return groupPosition;
-        }
-
-        @Override
-        public long getChildId(int groupPosition, int childPosition) {
-            return childPosition;
-        }
-
-        @Override
-        public boolean hasStableIds() {
-            return true;
-        }
-
-        @Override
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            if (convertView == null) {
-                convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
-            }
-            TextView factor = (TextView) convertView.findViewById(android.R.id.text1);
-            factor.setText(getGroup(groupPosition).toString());
-            return convertView;
-        }
-
-        @Override
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            // Here a custom child item is used instead of android.R.simple_list_item_2 because it
-            // is more customizable for this specific UI
-            if (convertView == null) {
-                convertView = mInflater.inflate(R.layout.tare_child_item, null);
-            }
-            TextView factor = (TextView) convertView.findViewById(R.id.factor);
-            TextView value = (TextView) convertView.findViewById(R.id.factor_number);
-
-            // TODO: Replace these hardcoded values with either default or user inputted TARE values
-            factor.setText(getChild(groupPosition, childPosition).toString());
-            value.setText("500");
-
-            return convertView;
-        }
-
-        @Override
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
+        mKeys = new String[][]{
+                {
+                        EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT,
+                        EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT
+                },
+                {
+                        EconomyManager.KEY_JS_MAX_SATIATED_BALANCE,
+                        EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
+                        EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+                        EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP
+                },
+                // {},
+                // {},
+                // {},
+        };
     }
 }
diff --git a/src/com/android/settings/development/tare/TareFactorController.java b/src/com/android/settings/development/tare/TareFactorController.java
index 50531a0..b9f813d 100644
--- a/src/com/android/settings/development/tare/TareFactorController.java
+++ b/src/com/android/settings/development/tare/TareFactorController.java
@@ -16,12 +16,21 @@
 
 package com.android.settings.development.tare;
 
+import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
+import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
+
+import android.annotation.NonNull;
 import android.app.tare.EconomyManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
@@ -34,6 +43,8 @@
 public class TareFactorController {
     private static final String TAG = "TareFactorController";
 
+    private static TareFactorController sInstance;
+
     private static final int POLICY_ALARM_MANAGER = 0;
     private static final int POLICY_JOB_SCHEDULER = 1;
 
@@ -45,15 +56,19 @@
     private String mAlarmManagerConstants;
     private String mJobSchedulerConstants;
 
-    public TareFactorController(Context context) {
+    private final ArraySet<DataChangeListener> mDataChangeListeners = new ArraySet<>();
+
+    private TareFactorController(Context context) {
         mContentResolver = context.getContentResolver();
         mResources = context.getResources();
 
-        mAlarmManagerConstants = Settings.Global
-                .getString(mContentResolver, Settings.Global.TARE_ALARM_MANAGER_CONSTANTS);
+        ConfigObserver configObserver = new ConfigObserver(new Handler(Looper.getMainLooper()));
+        configObserver.start();
 
-        mJobSchedulerConstants = Settings.Global
-                .getString(mContentResolver, Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS);
+        mAlarmManagerConstants =
+                Settings.Global.getString(mContentResolver, TARE_ALARM_MANAGER_CONSTANTS);
+        mJobSchedulerConstants =
+                Settings.Global.getString(mContentResolver, TARE_JOB_SCHEDULER_CONSTANTS);
 
         initAlarmManagerMap();
         parseAlarmManagerGlobalSettings();
@@ -62,30 +77,43 @@
         parseJobSchedulerGlobalSettings();
     }
 
+    static TareFactorController getInstance(Context context) {
+        synchronized (TareFactorController.class) {
+            if (sInstance == null) {
+                sInstance = new TareFactorController(context.getApplicationContext());
+            }
+        }
+        return sInstance;
+    }
+
     /**
      * Initialization for AlarmManager Map that sets a AM factor key to a title, default value, and
      * policy type in a data object.
      */
     private void initAlarmManagerMap() {
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
-                new TareFactorData(mResources.getString(R.string.tare_min_satiated_balance),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_exempted),
                         EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
-                new TareFactorData(mResources.getString(R.string.tare_headless_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_headless_app),
                         EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP,
-                new TareFactorData(mResources.getString(R.string.tare_other_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_other_app),
                         EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE,
                 new TareFactorData(mResources.getString(R.string.tare_max_satiated_balance),
                         EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE,
                         POLICY_ALARM_MANAGER));
-        mAlarmManagerMap.put(EconomyManager.KEY_AM_MAX_CIRCULATION,
-                new TareFactorData(mResources.getString(R.string.tare_max_circulation),
-                        EconomyManager.DEFAULT_AM_MAX_CIRCULATION,
+        mAlarmManagerMap.put(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT,
+                new TareFactorData(mResources.getString(R.string.tare_initial_consumption_limit),
+                        EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT,
+                        POLICY_ALARM_MANAGER));
+        mAlarmManagerMap.put(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT,
+                new TareFactorData(mResources.getString(R.string.tare_hard_consumption_limit),
+                        EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_REWARD_TOP_ACTIVITY_INSTANT,
                 new TareFactorData(mResources.getString(R.string.tare_top_activity),
@@ -250,24 +278,28 @@
      */
     private void initJobSchedulerMap() {
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
-                new TareFactorData(mResources.getString(R.string.tare_min_satiated_balance),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_exempted),
                         EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
-                new TareFactorData(mResources.getString(R.string.tare_headless_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_headless_app),
                         EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP,
-                new TareFactorData(mResources.getString(R.string.tare_other_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_other_app),
                         EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE,
                 new TareFactorData(mResources.getString(R.string.tare_max_satiated_balance),
                         EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE,
                         POLICY_JOB_SCHEDULER));
-        mJobSchedulerMap.put(EconomyManager.KEY_JS_MAX_CIRCULATION,
-                new TareFactorData(mResources.getString(R.string.tare_max_circulation),
-                        EconomyManager.DEFAULT_JS_MAX_CIRCULATION,
+        mJobSchedulerMap.put(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT,
+                new TareFactorData(mResources.getString(R.string.tare_initial_consumption_limit),
+                        EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT,
+                        POLICY_JOB_SCHEDULER));
+        mJobSchedulerMap.put(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT,
+                new TareFactorData(mResources.getString(R.string.tare_hard_consumption_limit),
+                        EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_REWARD_TOP_ACTIVITY_INSTANT,
                 new TareFactorData(mResources.getString(R.string.tare_top_activity),
@@ -408,8 +440,7 @@
         mJobSchedulerMap.put(
                 EconomyManager.KEY_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE,
                 new TareFactorData(mResources.getString(R.string.tare_job_low_running),
-                        EconomyManager
-                                .DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE,
+                        EconomyManager.DEFAULT_JS_ACTION_JOB_LOW_RUNNING_BASE_PRICE,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_PRICE,
                 new TareFactorData(mResources.getString(R.string.tare_job_min_start),
@@ -425,103 +456,65 @@
                         POLICY_JOB_SCHEDULER));
     }
 
-
-    /**
-     * Takes a key and factor policy as input and grabs the default value linked to it.
-     *
-     * @param key the key of the factor you want to get the default value of
-     * @param factorPolicy the policy you want the default value of
-     */
-    private int getDefaultValue(String key, int factorPolicy) {
-        ArrayMap<String, TareFactorData> currentMap;
-        switch (factorPolicy) {
-            case POLICY_ALARM_MANAGER:
-                currentMap = mAlarmManagerMap;
-                break;
-            case POLICY_JOB_SCHEDULER:
-                currentMap = mJobSchedulerMap;
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid factor policy given");
-        }
-        return currentMap.get(key).defaultValue;
-    }
-
     /**
      * Parses the AM constant from Settings.Global to get to the current value.
      */
     private void parseAlarmManagerGlobalSettings() {
-        try {
-            mParser.setString(mAlarmManagerConstants);
-        } catch (Exception e) {
-            Slog.e(TAG, "Bad value string constants", e);
-        }
-        int size = mParser.size();
-
-        for (int i = 0; i < size - 1; i++) {
-            String key = mParser.keyAt(i);
-            TareFactorData data = mAlarmManagerMap.get(key);
-            data.currentValue = mParser.getInt(key, getDefaultValue(key, getFactorType(key)));
-        }
+        parseSettingsIntoMap(mAlarmManagerConstants, mAlarmManagerMap);
     }
 
     /**
      * Parses the JS constant from Settings.Global to get to the current value.
      */
     private void parseJobSchedulerGlobalSettings() {
-        try {
-            mParser.setString(mJobSchedulerConstants);
-        } catch (Exception e) {
-            Slog.e(TAG, "Bad value string constants", e);
-        }
-        int size = mParser.size();
+        parseSettingsIntoMap(mJobSchedulerConstants, mJobSchedulerMap);
+    }
 
-        for (int i = 0; i < size - 1; i++) {
-            String key = mParser.keyAt(i);
-            TareFactorData data = mJobSchedulerMap.get(key);
-            data.currentValue = mParser.getInt(key, getDefaultValue(key, getFactorType(key)));
+    private void parseSettingsIntoMap(String constants, ArrayMap<String, TareFactorData> map) {
+        try {
+            mParser.setString(constants);
+        } catch (Exception e) {
+            Slog.e(TAG, "Bad string constants value", e);
+        }
+
+        for (int i = map.size() - 1; i >= 0; --i) {
+            final String key = map.keyAt(i);
+            final TareFactorData data = map.valueAt(i);
+            data.currentValue = mParser.getInt(key, data.defaultValue);
+        }
+    }
+
+    @NonNull
+    private ArrayMap<String, TareFactorData> getMap(int factorPolicy) {
+        switch (factorPolicy) {
+            case POLICY_ALARM_MANAGER:
+                return mAlarmManagerMap;
+            case POLICY_JOB_SCHEDULER:
+                return mJobSchedulerMap;
+            default:
+                throw new IllegalArgumentException("Invalid factor policy given");
         }
     }
 
     /**
      * Takes a key and factor policy as input and grabs the title linked to it.
      *
-     * @param key the key of the factor you want to get the title of
+     * @param key          the key of the factor you want to get the title of
      * @param factorPolicy the policy you want the title of
      */
     private String getTitle(String key, int factorPolicy) {
-        ArrayMap<String, TareFactorData> currentMap;
-        switch (factorPolicy) {
-            case POLICY_ALARM_MANAGER:
-                currentMap = mAlarmManagerMap;
-                break;
-            case POLICY_JOB_SCHEDULER:
-                currentMap = mJobSchedulerMap;
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid factor policy given");
-        }
+        final ArrayMap<String, TareFactorData> currentMap = getMap(factorPolicy);
         return currentMap.get(key).title;
     }
 
     /**
      * Takes a key and factor policy as input and grabs the current value linked to it.
      *
-     * @param key the key of the factor you want to get the default value of
+     * @param key          the key of the factor you want to get the default value of
      * @param factorPolicy the policy you want the current value of
      */
     private int getCurrentValue(String key, int factorPolicy) {
-        ArrayMap<String, TareFactorData> currentMap;
-        switch (factorPolicy) {
-            case POLICY_ALARM_MANAGER:
-                currentMap = mAlarmManagerMap;
-                break;
-            case POLICY_JOB_SCHEDULER:
-                currentMap = mJobSchedulerMap;
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid factor policy given");
-        }
+        final ArrayMap<String, TareFactorData> currentMap = getMap(factorPolicy);
         return currentMap.get(key).currentValue;
     }
 
@@ -542,6 +535,11 @@
         return currentMap.get(key).factorPolicy;
     }
 
+    int getValue(String key) {
+        final int policy = getFactorType(key);
+        return getCurrentValue(key, policy);
+    }
+
     /**
      * Takes a key,edited value, and factor policy as input and assigns the new edited value to
      * be the new current value for that factors key.
@@ -551,20 +549,15 @@
      * @param factorPolicy policy being updated
      */
     public void updateValue(String key, int editedValue, int factorPolicy) {
-        switch (factorPolicy) {
-            case POLICY_ALARM_MANAGER:
-                mAlarmManagerMap.get(key).currentValue = editedValue;
-                rebuildPolicyConstants(factorPolicy);
-                break;
-            case POLICY_JOB_SCHEDULER:
-                mJobSchedulerMap.get(key).currentValue = editedValue;
-                rebuildPolicyConstants(factorPolicy);
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid factor policy given");
-        }
-    }
+        final ArrayMap<String, TareFactorData> map = getMap(factorPolicy);
 
+        final TareFactorData data = map.get(key);
+        if (data.currentValue == editedValue) {
+            return;
+        }
+        data.currentValue = editedValue;
+        rebuildPolicyConstants(factorPolicy);
+    }
 
     /**
      * Iterates through the factor policy map for keys and current values to
@@ -573,58 +566,39 @@
      * @param factorPolicy policy being updated
      */
     private void rebuildPolicyConstants(int factorPolicy) {
-        StringBuilder newConstantsStringBuilder = new StringBuilder();
-
         switch (factorPolicy) {
             case POLICY_ALARM_MANAGER:
-                int sizeAM = mAlarmManagerMap.size();
-
-                for (int i = 0; i < sizeAM; i++) {
-                    if (i > 0) {
-                        newConstantsStringBuilder.append(",");
-                    }
-
-                    String key = mAlarmManagerMap.keyAt(i);
-                    newConstantsStringBuilder.append(key + "=" + mAlarmManagerMap.get(key)
-                            .currentValue);
-                }
-
-                String newAMConstantsString = newConstantsStringBuilder.toString();
-
-                Settings.Global.putString(mContentResolver, Settings.Global
-                                .TARE_ALARM_MANAGER_CONSTANTS,
-                        newAMConstantsString);
-
-                mAlarmManagerConstants = Settings.Global
-                        .getString(mContentResolver, Settings.Global
-                                .TARE_ALARM_MANAGER_CONSTANTS);
+                writeConstantsToSettings(mAlarmManagerMap, TARE_ALARM_MANAGER_CONSTANTS);
                 break;
             case POLICY_JOB_SCHEDULER:
-                int sizeJS = mJobSchedulerMap.size();
-
-                for (int i = 0; i < sizeJS; i++) {
-                    if (i > 0) {
-                        newConstantsStringBuilder.append(",");
-                    }
-
-                    String key = mJobSchedulerMap.keyAt(i);
-                    newConstantsStringBuilder.append(key + "=" + mJobSchedulerMap.get(key)
-                            .currentValue);
-                }
-
-                String newJSConstantsString = newConstantsStringBuilder.toString();
-
-                Settings.Global.putString(mContentResolver, Settings.Global
-                                .TARE_JOB_SCHEDULER_CONSTANTS,
-                        newJSConstantsString);
-
-                mJobSchedulerConstants = Settings.Global
-                        .getString(mContentResolver, Settings.Global
-                                .TARE_JOB_SCHEDULER_CONSTANTS);
+                writeConstantsToSettings(mJobSchedulerMap, TARE_JOB_SCHEDULER_CONSTANTS);
                 break;
         }
     }
 
+    private void writeConstantsToSettings(ArrayMap<String, TareFactorData> factorMap,
+            String settingsKey) {
+        final StringBuilder constantsStringBuilder = new StringBuilder();
+
+        for (int i = 0, size = factorMap.size(); i < size; ++i) {
+            final TareFactorData factor = factorMap.valueAt(i);
+            if (factor.currentValue == factor.defaultValue) {
+                continue;
+            }
+
+            if (constantsStringBuilder.length() > 0) {
+                constantsStringBuilder.append(",");
+            }
+
+            constantsStringBuilder
+                    .append(factorMap.keyAt(i))
+                    .append("=")
+                    .append(factor.currentValue);
+        }
+
+        Settings.Global.putString(mContentResolver, settingsKey, constantsStringBuilder.toString());
+    }
+
     /**
      * Creates a dialog with the values linked to the key.
      *
@@ -633,7 +607,7 @@
     public TareFactorDialogFragment createDialog(String key) {
         int policy = getFactorType(key);
         return new TareFactorDialogFragment(getTitle(key, policy), key,
-                getCurrentValue(key, policy), policy , this);
+                getCurrentValue(key, policy), policy, this);
     }
 
     /**
@@ -652,4 +626,51 @@
             this.currentValue = defaultValue;
         }
     }
-}
\ No newline at end of file
+
+    interface DataChangeListener {
+        void onDataChanged();
+    }
+
+    void registerListener(DataChangeListener listener) {
+        mDataChangeListeners.add(listener);
+    }
+
+    void unregisterListener(DataChangeListener listener) {
+        mDataChangeListeners.remove(listener);
+    }
+
+    void notifyListeners() {
+        for (int i = mDataChangeListeners.size() - 1; i >= 0; --i) {
+            mDataChangeListeners.valueAt(i).onDataChanged();
+        }
+    }
+
+    private class ConfigObserver extends ContentObserver {
+
+        ConfigObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void start() {
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS), false, this);
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (uri.equals(Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS))) {
+                mAlarmManagerConstants =
+                        Settings.Global.getString(mContentResolver, TARE_ALARM_MANAGER_CONSTANTS);
+                parseAlarmManagerGlobalSettings();
+                notifyListeners();
+            } else if (uri.equals(Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS))) {
+                mJobSchedulerConstants =
+                        Settings.Global.getString(mContentResolver, TARE_JOB_SCHEDULER_CONSTANTS);
+                parseJobSchedulerGlobalSettings();
+                notifyListeners();
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/development/tare/TareFactorDialogFragment.java b/src/com/android/settings/development/tare/TareFactorDialogFragment.java
index ff7f5f9..8d2f341 100644
--- a/src/com/android/settings/development/tare/TareFactorDialogFragment.java
+++ b/src/com/android/settings/development/tare/TareFactorDialogFragment.java
@@ -16,7 +16,10 @@
 
 package com.android.settings.development.tare;
 
+import android.annotation.NonNull;
+import android.app.AlertDialog;
 import android.app.Dialog;
+import android.app.DialogFragment;
 import android.content.Context;
 import android.os.Bundle;
 import android.text.InputType;
@@ -25,10 +28,6 @@
 import android.view.View;
 import android.widget.EditText;
 
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-
 import com.android.settings.R;
 import com.android.settings.Utils;
 
diff --git a/src/com/android/settings/development/tare/TareFactorExpandableListAdapter.java b/src/com/android/settings/development/tare/TareFactorExpandableListAdapter.java
new file mode 100644
index 0000000..8fe4c05
--- /dev/null
+++ b/src/com/android/settings/development/tare/TareFactorExpandableListAdapter.java
@@ -0,0 +1,143 @@
+/*
+ * 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.development.tare;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * Creates the expandable list that will allow modifying individual factors.
+ */
+public class TareFactorExpandableListAdapter extends BaseExpandableListAdapter {
+
+    private final LayoutInflater mLayoutInflater;
+    private final TareFactorController mFactorController;
+
+    private final String[] mGroups;
+    private final String[][] mChildren;
+    private final String[][] mKeys;
+
+    TareFactorExpandableListAdapter(TareFactorController factorController,
+            LayoutInflater layoutInflater, String[] groups, String[][] children, String[][] keys) {
+        mLayoutInflater = layoutInflater;
+        mFactorController = factorController;
+
+        mGroups = groups;
+        mChildren = children;
+        mKeys = keys;
+
+        validateMappings();
+    }
+
+    private void validateMappings() {
+        if (mGroups.length != mChildren.length) {
+            throw new IllegalStateException("groups and children don't have the same length");
+        }
+        if (mChildren.length != mKeys.length) {
+            throw new IllegalStateException("children and keys don't have the same length");
+        }
+        for (int i = 0; i < mChildren.length; ++i) {
+            if (mChildren[i].length != mKeys[i].length) {
+                throw new IllegalStateException(
+                        "children and keys don't have the same length in row " + i);
+            }
+        }
+    }
+
+    @Override
+    public int getGroupCount() {
+        return mGroups.length;
+    }
+
+    @Override
+    public int getChildrenCount(int groupPosition) {
+        return mChildren[groupPosition].length;
+    }
+
+    @Override
+    public Object getGroup(int groupPosition) {
+        return mGroups[groupPosition];
+    }
+
+    @Override
+    public Object getChild(int groupPosition, int childPosition) {
+        return mChildren[groupPosition][childPosition];
+    }
+
+    @Override
+    public long getGroupId(int groupPosition) {
+        return groupPosition;
+    }
+
+    @Override
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    @NonNull
+    String getKey(int groupPosition, int childPosition) {
+        return mKeys[groupPosition][childPosition];
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+            ViewGroup parent) {
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(android.R.layout.simple_list_item_1, parent,
+                    false);
+        }
+        TextView factor = convertView.findViewById(android.R.id.text1);
+        factor.setText(getGroup(groupPosition).toString());
+        return convertView;
+    }
+
+    @Override
+    @SuppressLint("InflateParams") // AdapterView doesn't support addView
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent) {
+        // Here a custom child item is used instead of android.R.simple_list_item_2 because it
+        // is more customizable for this specific UI
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(R.layout.tare_child_item, null);
+        }
+        TextView factor = convertView.findViewById(R.id.factor);
+        TextView value = convertView.findViewById(R.id.factor_number);
+
+        factor.setText(getChild(groupPosition, childPosition).toString());
+        value.setText(String.valueOf(
+                mFactorController.getValue(getKey(groupPosition, childPosition))));
+
+        return convertView;
+    }
+
+    @Override
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+}
diff --git a/src/com/android/settings/development/tare/TareHomePage.java b/src/com/android/settings/development/tare/TareHomePage.java
index 38e7ed8..cea0954 100644
--- a/src/com/android/settings/development/tare/TareHomePage.java
+++ b/src/com/android/settings/development/tare/TareHomePage.java
@@ -69,11 +69,14 @@
     }
 
     /** Reverts the TARE settings to the original default settings */
-    // TODO: Establish default TARE values and make this method revert all settings back to default.
     public void revertSettings(View v) {
         Toast.makeText(this, R.string.tare_settings_reverted_toast, Toast.LENGTH_LONG).show();
         Settings.Global.putString(getApplicationContext().getContentResolver(),
                 Settings.Global.ENABLE_TARE, null);
+        Settings.Global.putString(getApplicationContext().getContentResolver(),
+                Settings.Global.TARE_ALARM_MANAGER_CONSTANTS, null);
+        Settings.Global.putString(getApplicationContext().getContentResolver(),
+                Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS, null);
         setEnabled(Settings.Global.DEFAULT_ENABLE_TARE == SETTING_VALUE_ON);
     }
 
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index 746d234..cf31724 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -47,6 +47,7 @@
 import com.android.settings.deviceinfo.storage.DiskInitFragment;
 import com.android.settings.deviceinfo.storage.SecondaryUserController;
 import com.android.settings.deviceinfo.storage.StorageAsyncLoader;
+import com.android.settings.deviceinfo.storage.StorageCacheHelper;
 import com.android.settings.deviceinfo.storage.StorageEntry;
 import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
 import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController;
@@ -109,6 +110,8 @@
     private boolean mIsWorkProfile;
     private int mUserId;
     private Preference mFreeUpSpacePreference;
+    private boolean mIsLoadedFromCache;
+    private StorageCacheHelper mStorageCacheHelper;
 
     private final StorageEventListener mStorageEventListener = new StorageEventListener() {
         @Override
@@ -239,15 +242,27 @@
             mPreferenceController.setVolume(null);
             return;
         }
+
+        if (mStorageCacheHelper.hasCachedSizeInfo() && mSelectedStorageEntry.isPrivate()) {
+            StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
+            mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
+            mPreferenceController.setUsedSize(cachedData.usedSize);
+            mPreferenceController.setTotalSize(cachedData.totalSize);
+        }
+
         if (mSelectedStorageEntry.isPrivate()) {
             mStorageInfo = null;
             mAppsResult = null;
-            maybeSetLoading(isQuotaSupported());
-
-            // To prevent flicker, sets null volume to hide category preferences.
-            // onReceivedSizes will setVolume with the volume of selected storage.
-            mPreferenceController.setVolume(null);
-
+            // Hide the loading spinner if there is cached data.
+            if (mStorageCacheHelper.hasCachedSizeInfo()) {
+                //TODO(b/220259287): apply cache mechanism to secondary user
+                mPreferenceController.onLoadFinished(mAppsResult, mUserId);
+            } else {
+                maybeSetLoading(isQuotaSupported());
+                // To prevent flicker, sets null volume to hide category preferences.
+                // onReceivedSizes will setVolume with the volume of selected storage.
+                mPreferenceController.setVolume(null);
+            }
             // Stats data is only available on private volumes.
             getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
             getLoaderManager()
@@ -277,6 +292,16 @@
 
         initializePreference();
         initializeOptionsMenu(activity);
+
+        if (mStorageCacheHelper.hasCachedSizeInfo()) {
+            mIsLoadedFromCache = true;
+            mStorageEntries.clear();
+            mStorageEntries.addAll(
+                    StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
+            refreshUi();
+            updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
+            setSecondaryUsersVisible(true);
+        }
     }
 
     private void initializePreference() {
@@ -291,6 +316,7 @@
         mUserManager = context.getSystemService(UserManager.class);
         mIsWorkProfile = false;
         mUserId = UserHandle.myUserId();
+        mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId);
 
         super.onAttach(context);
         use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager(
@@ -323,9 +349,14 @@
     public void onResume() {
         super.onResume();
 
-        mStorageEntries.clear();
-        mStorageEntries.addAll(StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
-        refreshUi();
+        if (mIsLoadedFromCache) {
+            mIsLoadedFromCache = false;
+        } else {
+            mStorageEntries.clear();
+            mStorageEntries.addAll(
+                    StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
+            refreshUi();
+        }
         mStorageManager.registerListener(mStorageEventListener);
     }
 
@@ -333,6 +364,11 @@
     public void onPause() {
         super.onPause();
         mStorageManager.unregisterListener(mStorageEventListener);
+        // Destroy the data loaders to prevent unnecessary data loading when switching back to the
+        // page.
+        getLoaderManager().destroyLoader(STORAGE_JOB_ID);
+        getLoaderManager().destroyLoader(ICON_JOB_ID);
+        getLoaderManager().destroyLoader(VOLUME_SIZE_JOB_ID);
     }
 
     @Override
@@ -359,6 +395,8 @@
         mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
         mPreferenceController.setUsedSize(privateUsedBytes);
         mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
+        // Cache total size and used size
+        mStorageCacheHelper.cacheTotalSizeAndUsedSize(mStorageInfo.totalBytes, privateUsedBytes);
         for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
             final AbstractPreferenceController controller = mSecondaryUsers.get(i);
             if (controller instanceof SecondaryUserController) {
diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java
index 934ff3f..d3549d4 100644
--- a/src/com/android/settings/deviceinfo/StorageItemPreference.java
+++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java
@@ -66,7 +66,7 @@
             return;
 
         mProgressBar.setMax(PROGRESS_MAX);
-        mProgressBar.setProgress(mProgressPercent);
+        mProgressBar.setProgress(mProgressPercent, true /* animate */);
     }
 
     @Override
diff --git a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java
index f87a06a..84cafd4 100644
--- a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java
+++ b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java
@@ -183,6 +183,9 @@
 
     @Override
     public void handleResult(SparseArray<StorageAsyncLoader.StorageResult> stats) {
+        if (stats == null) {
+            return;
+        }
         final StorageAsyncLoader.StorageResult result = stats.get(getUser().id);
         if (result != null) {
             setSize(result.externalStats.totalBytes);
diff --git a/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java b/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java
new file mode 100644
index 0000000..a868c9f
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/storage/StorageCacheHelper.java
@@ -0,0 +1,115 @@
+/*
+ * 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.deviceinfo.storage;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * A utility class to cache and restore the storage size information.
+ */
+public class StorageCacheHelper {
+
+    private static final String SHARED_PREFERENCE_NAME = "StorageCache";
+    private static final String TOTAL_SIZE_KEY = "total_size_key";
+    private static final String USED_SIZE_KEY = "used_size_key";
+    private static final String IMAGES_SIZE_KEY = "images_size_key";
+    private static final String VIDEOS_SIZE_KEY = "videos_size_key";
+    private static final String AUDIO_SIZE_KEY = "audio_size_key";
+    private static final String APPS_SIZE_KEY = "apps_size_key";
+    private static final String GAMES_SIZE_KEY = "games_size_key";
+    private static final String DOCUMENTS_AND_OTHER_SIZE_KEY = "documents_and_other_size_key";
+    private static final String TRASH_SIZE_KEY = "trash_size_key";
+    private static final String SYSTEM_SIZE_KEY = "system_size_key";
+
+    private final SharedPreferences mSharedPreferences;
+
+    public StorageCacheHelper(Context context, int userId) {
+        String sharedPrefName = SHARED_PREFERENCE_NAME + userId;
+        mSharedPreferences = context.getSharedPreferences(sharedPrefName, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Returns true if there's a cached size info.
+     */
+    public boolean hasCachedSizeInfo() {
+        return mSharedPreferences.getAll().size() > 0;
+    }
+
+    /**
+     * Cache the size info
+     * @param data a data about the file size info.
+     */
+    public void cacheSizeInfo(StorageCache data) {
+        mSharedPreferences
+                .edit()
+                .putLong(IMAGES_SIZE_KEY, data.imagesSize)
+                .putLong(VIDEOS_SIZE_KEY, data.videosSize)
+                .putLong(AUDIO_SIZE_KEY, data.audioSize)
+                .putLong(APPS_SIZE_KEY, data.allAppsExceptGamesSize)
+                .putLong(GAMES_SIZE_KEY, data.gamesSize)
+                .putLong(DOCUMENTS_AND_OTHER_SIZE_KEY, data.documentsAndOtherSize)
+                .putLong(TRASH_SIZE_KEY, data.trashSize)
+                .putLong(SYSTEM_SIZE_KEY, data.systemSize)
+                .apply();
+    }
+
+    /**
+     * Cache total size and used size
+     */
+    public void cacheTotalSizeAndUsedSize(long totalSize, long usedSize) {
+        mSharedPreferences
+                .edit()
+                .putLong(TOTAL_SIZE_KEY, totalSize)
+                .putLong(USED_SIZE_KEY, usedSize)
+                .apply();
+    }
+
+    /**
+     * Returns a cached data about all file size information.
+     */
+    public StorageCache retrieveCachedSize() {
+        StorageCache result = new StorageCache();
+        result.totalSize = mSharedPreferences.getLong(TOTAL_SIZE_KEY, 0);
+        result.usedSize = mSharedPreferences.getLong(USED_SIZE_KEY, 0);
+        result.imagesSize = mSharedPreferences.getLong(IMAGES_SIZE_KEY, 0);
+        result.videosSize = mSharedPreferences.getLong(VIDEOS_SIZE_KEY, 0);
+        result.audioSize = mSharedPreferences.getLong(AUDIO_SIZE_KEY, 0);
+        result.allAppsExceptGamesSize = mSharedPreferences.getLong(APPS_SIZE_KEY, 0);
+        result.gamesSize = mSharedPreferences.getLong(GAMES_SIZE_KEY, 0);
+        result.documentsAndOtherSize = mSharedPreferences.getLong(DOCUMENTS_AND_OTHER_SIZE_KEY, 0);
+        result.trashSize = mSharedPreferences.getLong(TRASH_SIZE_KEY, 0);
+        result.systemSize = mSharedPreferences.getLong(SYSTEM_SIZE_KEY, 0);
+        return result;
+    }
+
+    /**
+     *  All the cached data about the file size information.
+     */
+    public static class StorageCache {
+        public long totalSize;
+        public long usedSize;
+        public long gamesSize;
+        public long allAppsExceptGamesSize;
+        public long audioSize;
+        public long imagesSize;
+        public long videosSize;
+        public long documentsAndOtherSize;
+        public long trashSize;
+        public long systemSize;
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index ee0c9e7..9813439 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -34,6 +34,7 @@
 import android.util.SparseArray;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.Fragment;
 import androidx.preference.Preference;
@@ -135,7 +136,11 @@
 
     private boolean mIsWorkProfile;
 
-    private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
+    private StorageCacheHelper mStorageCacheHelper;
+    // The mIsDocumentsPrefShown being used here is to prevent a flicker problem from displaying
+    // the Document entry.
+    private boolean mIsDocumentsPrefShown;
+    private boolean mIsPreferenceOrderedBySize;
 
     public StorageItemPreferenceController(Context context, Fragment hostFragment,
             VolumeInfo volume, StorageVolumeProvider svp, boolean isWorkProfile) {
@@ -148,6 +153,8 @@
         mIsWorkProfile = isWorkProfile;
         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
         mUserId = getCurrentUserId();
+        mIsDocumentsPrefShown = isDocumentsPrefShown();
+        mStorageCacheHelper = new StorageCacheHelper(mContext, mUserId);
 
         mImagesUri = Uri.parse(context.getResources()
                 .getString(R.string.config_images_storage_category_uri));
@@ -267,14 +274,17 @@
         // If we don't have a shared volume for our internal storage (or the shared volume isn't
         // mounted as readable for whatever reason), we should hide the File preference.
         if (visible) {
-            final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
-            mDocumentsAndOtherPreference.setVisible(sharedVolume != null
-                    && sharedVolume.isMountedReadable());
+            mDocumentsAndOtherPreference.setVisible(mIsDocumentsPrefShown);
         } else {
             mDocumentsAndOtherPreference.setVisible(false);
         }
     }
 
+    private boolean isDocumentsPrefShown() {
+        VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
+        return sharedVolume != null && sharedVolume.isMountedReadable();
+    }
+
     private void updatePrivateStorageCategoryPreferencesOrder() {
         if (mScreen == null || !isValidPrivateVolume()) {
             return;
@@ -360,44 +370,74 @@
         mTrashPreference = screen.findPreference(TRASH_KEY);
     }
 
-    /** Fragments use it to set storage result and update UI of this controller. */
-    public void onLoadFinished(SparseArray<StorageAsyncLoader.StorageResult> result, int userId) {
-        final StorageAsyncLoader.StorageResult data = result.get(userId);
-
-        mImagesPreference.setStorageSize(data.imagesSize, mTotalSize);
-        mVideosPreference.setStorageSize(data.videosSize, mTotalSize);
-        mAudioPreference.setStorageSize(data.audioSize, mTotalSize);
-        mAppsPreference.setStorageSize(data.allAppsExceptGamesSize, mTotalSize);
-        mGamesPreference.setStorageSize(data.gamesSize, mTotalSize);
-        mDocumentsAndOtherPreference.setStorageSize(data.documentsAndOtherSize, mTotalSize);
-        mTrashPreference.setStorageSize(data.trashSize, mTotalSize);
-
+    /**
+     * Fragments use it to set storage result and update UI of this controller.
+     * @param result The StorageResult from StorageAsyncLoader. This allows a nullable result.
+     *               When it's null, the cached storage size info will be used instead.
+     * @param userId User ID to get the storage size info
+     */
+    public void onLoadFinished(@Nullable SparseArray<StorageAsyncLoader.StorageResult> result,
+            int userId) {
+        // Calculate the size info for each category
+        StorageCacheHelper.StorageCache storageCache = getSizeInfo(result, userId);
+        // Set size info to each preference
+        mImagesPreference.setStorageSize(storageCache.imagesSize, mTotalSize);
+        mVideosPreference.setStorageSize(storageCache.videosSize, mTotalSize);
+        mAudioPreference.setStorageSize(storageCache.audioSize, mTotalSize);
+        mAppsPreference.setStorageSize(storageCache.allAppsExceptGamesSize, mTotalSize);
+        mGamesPreference.setStorageSize(storageCache.gamesSize, mTotalSize);
+        mDocumentsAndOtherPreference.setStorageSize(storageCache.documentsAndOtherSize, mTotalSize);
+        mTrashPreference.setStorageSize(storageCache.trashSize, mTotalSize);
         if (mSystemPreference != null) {
-            // Everything else that hasn't already been attributed is tracked as
-            // belonging to system.
-            long attributedSize = 0;
-            for (int i = 0; i < result.size(); i++) {
-                final StorageAsyncLoader.StorageResult otherData = result.valueAt(i);
-                attributedSize +=
-                        otherData.gamesSize
-                                + otherData.audioSize
-                                + otherData.videosSize
-                                + otherData.imagesSize
-                                + otherData.documentsAndOtherSize
-                                + otherData.trashSize
-                                + otherData.allAppsExceptGamesSize;
-                attributedSize -= otherData.duplicateCodeSize;
-            }
-
-            final long systemSize = Math.max(DataUnit.GIBIBYTES.toBytes(1),
-                    mUsedBytes - attributedSize);
-            mSystemPreference.setStorageSize(systemSize, mTotalSize);
+            mSystemPreference.setStorageSize(storageCache.systemSize, mTotalSize);
+        }
+        // Cache the size info
+        if (result != null) {
+            mStorageCacheHelper.cacheSizeInfo(storageCache);
         }
 
-        updatePrivateStorageCategoryPreferencesOrder();
+        // Sort the preference according to size info in descending order
+        if (!mIsPreferenceOrderedBySize) {
+            updatePrivateStorageCategoryPreferencesOrder();
+            mIsPreferenceOrderedBySize = true;
+        }
         setPrivateStorageCategoryPreferencesVisibility(true);
     }
 
+    private StorageCacheHelper.StorageCache getSizeInfo(
+            SparseArray<StorageAsyncLoader.StorageResult> result, int userId) {
+        if (result == null) {
+            return mStorageCacheHelper.retrieveCachedSize();
+        }
+        StorageAsyncLoader.StorageResult data = result.get(userId);
+        StorageCacheHelper.StorageCache storageCache = new StorageCacheHelper.StorageCache();
+        storageCache.imagesSize = data.imagesSize;
+        storageCache.videosSize = data.videosSize;
+        storageCache.audioSize = data.audioSize;
+        storageCache.allAppsExceptGamesSize = data.allAppsExceptGamesSize;
+        storageCache.gamesSize = data.gamesSize;
+        storageCache.documentsAndOtherSize = data.documentsAndOtherSize;
+        storageCache.trashSize = data.trashSize;
+        // Everything else that hasn't already been attributed is tracked as
+        // belonging to system.
+        long attributedSize = 0;
+        for (int i = 0; i < result.size(); i++) {
+            final StorageAsyncLoader.StorageResult otherData = result.valueAt(i);
+            attributedSize +=
+                    otherData.gamesSize
+                            + otherData.audioSize
+                            + otherData.videosSize
+                            + otherData.imagesSize
+                            + otherData.documentsAndOtherSize
+                            + otherData.trashSize
+                            + otherData.allAppsExceptGamesSize;
+            attributedSize -= otherData.duplicateCodeSize;
+        }
+        storageCache.systemSize = Math.max(DataUnit.GIBIBYTES.toBytes(1),
+                mUsedBytes - attributedSize);
+        return storageCache;
+    }
+
     public void setUsedSize(long usedSizeBytes) {
         mUsedBytes = usedSizeBytes;
     }
diff --git a/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java
index 7660353..872fa8a 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageUsageProgressBarPreferenceController.java
@@ -18,6 +18,7 @@
 
 import android.app.usage.StorageStatsManager;
 import android.content.Context;
+import android.os.UserHandle;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -47,11 +48,13 @@
     private UsageProgressBarPreference mUsageProgressBarPreference;
     private StorageEntry mStorageEntry;
     boolean mIsUpdateStateFromSelectedStorageEntry;
+    private StorageCacheHelper mStorageCacheHelper;
 
     public StorageUsageProgressBarPreferenceController(Context context, String key) {
         super(context, key);
 
         mStorageStatsManager = context.getSystemService(StorageStatsManager.class);
+        mStorageCacheHelper = new StorageCacheHelper(context, UserHandle.myUserId());
     }
 
     /** Set StorageEntry to display. */
@@ -71,6 +74,15 @@
     }
 
     private void getStorageStatsAndUpdateUi() {
+        // Use cached data for both total size and used size.
+        if (mStorageEntry != null && mStorageEntry.isMounted() && mStorageEntry.isPrivate()) {
+            StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
+            mTotalBytes = cachedData.totalSize;
+            mUsedBytes = cachedData.usedSize;
+            mIsUpdateStateFromSelectedStorageEntry = true;
+            updateState(mUsageProgressBarPreference);
+        }
+        // Get the latest data from StorageStatsManager.
         ThreadUtils.postOnBackgroundThread(() -> {
             try {
                 if (mStorageEntry == null || !mStorageEntry.isMounted()) {
diff --git a/src/com/android/settings/display/AutoRotatePreferenceController.java b/src/com/android/settings/display/AutoRotatePreferenceController.java
index 5dc2286..90423fb 100644
--- a/src/com/android/settings/display/AutoRotatePreferenceController.java
+++ b/src/com/android/settings/display/AutoRotatePreferenceController.java
@@ -74,6 +74,7 @@
     @Override
     public int getAvailabilityStatus() {
         return RotationPolicy.isRotationLockToggleVisible(mContext)
+                && !DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)
                 ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
diff --git a/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragment.java b/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragment.java
new file mode 100644
index 0000000..fb6d9f4
--- /dev/null
+++ b/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragment.java
@@ -0,0 +1,76 @@
+/*
+ * 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.display;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.Indexable;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import java.util.List;
+
+/** Fragment that shows all the available device state based auto-rotation preferences. */
+@SearchIndexable
+public class DeviceStateAutoRotateDetailsFragment extends DashboardFragment {
+
+    private static final String TAG = "DeviceStateAutoRotateDetailsFragment";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.DISPLAY_AUTO_ROTATE_SETTINGS;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.device_state_auto_rotate_settings;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        DeviceStateAutoRotationHelper.initControllers(
+                getLifecycle(),
+                useAll(DeviceStateAutoRotateSettingController.class)
+        );
+    }
+
+    @Override
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        return DeviceStateAutoRotationHelper.createPreferenceControllers(context);
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.device_state_auto_rotate_settings) {
+
+                @Override
+                public List<SearchIndexableRaw> getRawDataToIndex(Context context,
+                        boolean enabled) {
+                    return DeviceStateAutoRotationHelper.getRawDataToIndex(context, enabled);
+                }
+            };
+}
diff --git a/src/com/android/settings/display/DeviceStateAutoRotateOverviewController.java b/src/com/android/settings/display/DeviceStateAutoRotateOverviewController.java
new file mode 100644
index 0000000..5e49bf3
--- /dev/null
+++ b/src/com/android/settings/display/DeviceStateAutoRotateOverviewController.java
@@ -0,0 +1,52 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * The top-level preference controller for device state based auto-rotation settings.
+ *
+ * It doesn't do anything on its own besides showing/hiding. The toggling of the settings will
+ * always be done in the details screen when device state based auto-rotation is enabled.
+ */
+public class DeviceStateAutoRotateOverviewController extends BasePreferenceController {
+
+    /** Preference key for when it is used in "accessibility_system_controls.xml". */
+    private static final String ACCESSIBILITY_PREF_KEY = "device_state_auto_rotate_accessibility";
+
+    public DeviceStateAutoRotateOverviewController(Context context, String key) {
+        super(context, key);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return isAvailableInternal() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    private boolean isAvailableInternal() {
+        return isA11yPage()
+                ? DeviceStateAutoRotationHelper.isDeviceStateRotationEnabledForA11y(mContext)
+                : DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext);
+    }
+
+    private boolean isA11yPage() {
+        return TextUtils.equals(getPreferenceKey(), ACCESSIBILITY_PREF_KEY);
+    }
+}
diff --git a/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java b/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java
new file mode 100644
index 0000000..c8f6280
--- /dev/null
+++ b/src/com/android/settings/display/DeviceStateAutoRotateSettingController.java
@@ -0,0 +1,141 @@
+/*
+ * 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.display;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import java.util.List;
+
+/** Controller for device state based auto rotation preferences. */
+public class DeviceStateAutoRotateSettingController extends TogglePreferenceController implements
+        LifecycleObserver {
+
+    private SwitchPreference mPreference;
+
+    private final DeviceStateRotationLockSettingsManager mAutoRotateSettingsManager;
+    private final int mOrder;
+    private final DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
+            mDeviceStateRotationLockSettingsListener = () -> updateState(mPreference);
+    private final int mDeviceState;
+    private final String mDeviceStateDescription;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+
+    public DeviceStateAutoRotateSettingController(Context context, int deviceState,
+            String deviceStateDescription, int order) {
+        super(context, getPreferenceKeyForDeviceState(deviceState));
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+        mDeviceState = deviceState;
+        mDeviceStateDescription = deviceStateDescription;
+        mAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(context);
+        mOrder = order;
+    }
+
+    void init(Lifecycle lifecycle) {
+        lifecycle.addObserver(this);
+    }
+
+    @OnLifecycleEvent(ON_START)
+    void onStart() {
+        mAutoRotateSettingsManager.registerListener(mDeviceStateRotationLockSettingsListener);
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    void onStop() {
+        mAutoRotateSettingsManager.unregisterListener(mDeviceStateRotationLockSettingsListener);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mPreference = new SwitchPreference(mContext);
+        mPreference.setTitle(mDeviceStateDescription);
+        mPreference.setKey(getPreferenceKey());
+        mPreference.setOrder(mOrder);
+        screen.addPreference(mPreference);
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)
+                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return getPreferenceKeyForDeviceState(mDeviceState);
+    }
+
+    private static String getPreferenceKeyForDeviceState(int deviceState) {
+        return "auto_rotate_device_state_" + deviceState;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return !mAutoRotateSettingsManager.isRotationLocked(mDeviceState);
+    }
+
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        boolean isRotationLocked = !isChecked;
+        mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_ROTATION_LOCK,
+                isRotationLocked);
+        mAutoRotateSettingsManager.updateSetting(mDeviceState, isRotationLocked);
+        return true;
+    }
+
+    @Override
+    public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
+        SearchIndexableRaw indexable = new SearchIndexableRaw(mContext);
+        indexable.key = getPreferenceKey();
+        indexable.title = mDeviceStateDescription;
+        // Maybe pass screen title as param?
+        indexable.screenTitle = mContext.getString(R.string.accelerometer_title);
+        rawData.add(indexable);
+    }
+
+    @Override
+    public int getSliceHighlightMenuRes() {
+        return R.string.menu_key_display;
+    }
+
+    @Override
+    public boolean isSliceable() {
+        return true; // Maybe set to false if in accessibility settings screen
+    }
+
+    @Override
+    public boolean isPublicSlice() {
+        return true;
+    }
+}
diff --git a/src/com/android/settings/display/DeviceStateAutoRotationHelper.java b/src/com/android/settings/display/DeviceStateAutoRotationHelper.java
new file mode 100644
index 0000000..223ef1a
--- /dev/null
+++ b/src/com/android/settings/display/DeviceStateAutoRotationHelper.java
@@ -0,0 +1,113 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.android.internal.view.RotationPolicy;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class with utility methods related to device state auto-rotation that can be used in
+ * auto-rotation settings fragments and controllers.
+ */
+public class DeviceStateAutoRotationHelper {
+
+    private static final String TAG = "DeviceStateAutoRotHelpr";
+
+    static void initControllers(Lifecycle lifecycle,
+            List<DeviceStateAutoRotateSettingController> controllers) {
+        for (DeviceStateAutoRotateSettingController controller : controllers) {
+            controller.init(lifecycle);
+        }
+    }
+
+    static ImmutableList<AbstractPreferenceController> createPreferenceControllers(
+            Context context) {
+        List<SettableDeviceState> settableDeviceStates = DeviceStateRotationLockSettingsManager
+                .getInstance(context).getSettableDeviceStates();
+        int numDeviceStates = settableDeviceStates.size();
+        if (numDeviceStates == 0) {
+            return ImmutableList.of();
+        }
+        String[] deviceStateSettingDescriptions = context.getResources().getStringArray(
+                R.array.config_settableAutoRotationDeviceStatesDescriptions);
+        if (numDeviceStates != deviceStateSettingDescriptions.length) {
+            Log.wtf(TAG,
+                    "Mismatch between number of device states and device states descriptions.");
+            return ImmutableList.of();
+        }
+
+        ImmutableList.Builder<AbstractPreferenceController> controllers =
+                ImmutableList.builderWithExpectedSize(numDeviceStates);
+        for (int i = 0; i < numDeviceStates; i++) {
+            SettableDeviceState settableDeviceState = settableDeviceStates.get(i);
+            if (!settableDeviceState.isSettable()) {
+                continue;
+            }
+            // Preferences with a lower order will be showed first. Here we go below 0 to make sure
+            // we are shown before statically declared preferences in XML.
+            int order = -numDeviceStates + i;
+            controllers.add(new DeviceStateAutoRotateSettingController(
+                    context,
+                    settableDeviceState.getDeviceState(),
+                    deviceStateSettingDescriptions[i],
+                    order
+            ));
+        }
+        return controllers.build();
+    }
+
+    static List<SearchIndexableRaw> getRawDataToIndex(
+            Context context, boolean enabled) {
+        // Check what the "enabled" param is for
+        List<AbstractPreferenceController> controllers = createPreferenceControllers(context);
+        List<SearchIndexableRaw> rawData = new ArrayList<>();
+        for (AbstractPreferenceController controller : controllers) {
+            ((BasePreferenceController) controller).updateRawDataToIndex(rawData);
+        }
+        return rawData;
+    }
+
+    /** Returns whether the device state based auto-rotation settings are enabled. */
+    public static boolean isDeviceStateRotationEnabled(Context context) {
+        return RotationPolicy.isRotationLockToggleVisible(context)
+                && DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(context);
+    }
+
+    /**
+     * Returns whether the device state based auto-rotation settings are enabled for the
+     * accessibility settings page.
+     */
+    public static boolean isDeviceStateRotationEnabledForA11y(Context context) {
+        return RotationPolicy.isRotationSupported(context)
+                && DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(context);
+    }
+}
diff --git a/src/com/android/settings/display/SmartAutoRotateController.java b/src/com/android/settings/display/SmartAutoRotateController.java
index 76a222a..d29a64e 100644
--- a/src/com/android/settings/display/SmartAutoRotateController.java
+++ b/src/com/android/settings/display/SmartAutoRotateController.java
@@ -47,6 +47,7 @@
 import com.android.settings.core.TogglePreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 
 /**
  * SmartAutoRotateController controls whether auto rotation is enabled
@@ -54,6 +55,8 @@
 public class SmartAutoRotateController extends TogglePreferenceController implements
         Preference.OnPreferenceChangeListener, LifecycleObserver {
 
+    protected Preference mPreference;
+
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final SensorPrivacyManager mPrivacyManager;
     private final PowerManager mPowerManager;
@@ -63,7 +66,9 @@
             updateState(mPreference);
         }
     };
-    protected Preference mPreference;
+    private final DeviceStateRotationLockSettingsManager mDeviceStateAutoRotateSettingsManager;
+    private final DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
+            mDeviceStateRotationLockSettingsListener = () -> updateState(mPreference);
     private RotationPolicy.RotationPolicyListener mRotationPolicyListener;
 
     public SmartAutoRotateController(Context context, String preferenceKey) {
@@ -73,6 +78,8 @@
         mPrivacyManager
                 .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> updateState(mPreference));
         mPowerManager = context.getSystemService(PowerManager.class);
+        mDeviceStateAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(
+                context);
     }
 
     public void init(Lifecycle lifecycle) {
@@ -89,6 +96,9 @@
     }
 
     protected boolean isRotationLocked() {
+        if (DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)) {
+            return mDeviceStateAutoRotateSettingsManager.isRotationLockedForAllStates();
+        }
         return RotationPolicy.isRotationLocked(mContext);
     }
 
@@ -127,6 +137,8 @@
             };
         }
         RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener);
+        mDeviceStateAutoRotateSettingsManager.registerListener(
+                mDeviceStateRotationLockSettingsListener);
     }
 
     @OnLifecycleEvent(ON_STOP)
@@ -136,6 +148,8 @@
             RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener);
             mRotationPolicyListener = null;
         }
+        mDeviceStateAutoRotateSettingsManager.unregisterListener(
+                mDeviceStateRotationLockSettingsListener);
     }
 
     @Override
diff --git a/src/com/android/settings/display/SmartAutoRotatePreferenceController.java b/src/com/android/settings/display/SmartAutoRotatePreferenceController.java
index bd8ee84..d02e336 100644
--- a/src/com/android/settings/display/SmartAutoRotatePreferenceController.java
+++ b/src/com/android/settings/display/SmartAutoRotatePreferenceController.java
@@ -77,6 +77,7 @@
     @Override
     public int getAvailabilityStatus() {
         return RotationPolicy.isRotationLockToggleVisible(mContext)
+                && !DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)
                 ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
diff --git a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java
index 4737336..9fda03c 100644
--- a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java
+++ b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java
@@ -34,10 +34,14 @@
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.search.Indexable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
 import com.android.settingslib.widget.FooterPreference;
 
+import java.util.List;
+
 /**
  * Preference fragment used for auto rotation
  */
@@ -60,6 +64,15 @@
     public void onAttach(Context context) {
         super.onAttach(context);
         use(SmartAutoRotateController.class).init(getLifecycle());
+        DeviceStateAutoRotationHelper.initControllers(
+                getLifecycle(),
+                useAll(DeviceStateAutoRotateSettingController.class)
+        );
+    }
+
+    @Override
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        return DeviceStateAutoRotationHelper.createPreferenceControllers(context);
     }
 
     @Override
@@ -79,7 +92,9 @@
 
     @VisibleForTesting
     void createHeader(SettingsActivity activity) {
-        if (isRotationResolverServiceAvailable(activity)) {
+        boolean deviceStateRotationEnabled =
+                DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(activity);
+        if (isRotationResolverServiceAvailable(activity) && !deviceStateRotationEnabled) {
             final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
             switchBar.setTitle(
                     getContext().getString(R.string.auto_rotate_settings_primary_switch_title));
@@ -127,5 +142,12 @@
     }
 
     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider(R.xml.auto_rotate_settings);
+            new BaseSearchIndexProvider(R.xml.auto_rotate_settings) {
+
+                @Override
+                public List<SearchIndexableRaw> getRawDataToIndex(
+                        Context context, boolean enabled) {
+                    return DeviceStateAutoRotationHelper.getRawDataToIndex(context, enabled);
+                }
+            };
 }
diff --git a/src/com/android/settings/dream/DreamAdapter.java b/src/com/android/settings/dream/DreamAdapter.java
index 4e16ccb..8e487d4 100644
--- a/src/com/android/settings/dream/DreamAdapter.java
+++ b/src/com/android/settings/dream/DreamAdapter.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.VectorDrawable;
+import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -45,8 +46,8 @@
      * View holder for each {@link IDreamItem}.
      */
     private class DreamViewHolder extends RecyclerView.ViewHolder {
-        private final ImageView mIconView;
         private final TextView mTitleView;
+        private final TextView mSummaryView;
         private final ImageView mPreviewView;
         private final ImageView mPreviewPlaceholderView;
         private final Button mCustomizeButton;
@@ -57,8 +58,8 @@
             mContext = context;
             mPreviewView = view.findViewById(R.id.preview);
             mPreviewPlaceholderView = view.findViewById(R.id.preview_placeholder);
-            mIconView = view.findViewById(R.id.icon);
             mTitleView = view.findViewById(R.id.title_text);
+            mSummaryView = view.findViewById(R.id.summary_text);
             mCustomizeButton = view.findViewById(R.id.customize_button);
         }
 
@@ -68,6 +69,14 @@
         public void bindView(IDreamItem item, int position) {
             mTitleView.setText(item.getTitle());
 
+            final CharSequence summary = item.getSummary();
+            if (TextUtils.isEmpty(summary)) {
+                mSummaryView.setVisibility(View.GONE);
+            } else {
+                mSummaryView.setText(summary);
+                mSummaryView.setVisibility(View.VISIBLE);
+            }
+
             final Drawable previewImage = item.getPreviewImage();
             if (previewImage != null) {
                 mPreviewView.setImageDrawable(previewImage);
@@ -82,7 +91,10 @@
                 icon.setTint(Utils.getColorAttrDefaultColor(mContext,
                         com.android.internal.R.attr.colorAccentPrimaryVariant));
             }
-            mIconView.setImageDrawable(icon);
+            final int iconSize = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.dream_item_icon_size);
+            icon.setBounds(0, 0, iconSize, iconSize);
+            mTitleView.setCompoundDrawablesRelative(icon, null, null, null);
 
             if (item.isActive()) {
                 mLastSelectedPos = position;
diff --git a/src/com/android/settings/dream/DreamPickerController.java b/src/com/android/settings/dream/DreamPickerController.java
index b7ba614..af8c364 100644
--- a/src/com/android/settings/dream/DreamPickerController.java
+++ b/src/com/android/settings/dream/DreamPickerController.java
@@ -112,6 +112,11 @@
         }
 
         @Override
+        public CharSequence getSummary() {
+            return mDreamInfo.description;
+        }
+
+        @Override
         public Drawable getIcon() {
             return mDreamInfo.icon;
         }
diff --git a/src/com/android/settings/dream/IDreamItem.java b/src/com/android/settings/dream/IDreamItem.java
index c462fe2..49c82be 100644
--- a/src/com/android/settings/dream/IDreamItem.java
+++ b/src/com/android/settings/dream/IDreamItem.java
@@ -18,23 +18,52 @@
 
 import android.graphics.drawable.Drawable;
 
+import androidx.annotation.Nullable;
+
 /**
  * Interface representing a dream item to be displayed.
  */
 public interface IDreamItem {
+    /**
+     * Gets the title of this dream.
+     */
     CharSequence getTitle();
 
+    /**
+     * Gets the summary of this dream, or null if the dream doesn't provide one.
+     */
+    @Nullable
+    CharSequence getSummary();
+
+    /**
+     * Gets the icon for the dream.
+     */
     Drawable getIcon();
 
+    /**
+     * Callback which can be implemented to handle clicks on this dream.
+     */
     void onItemClicked();
 
+    /**
+     * Callback which can be implemented to handle the customization of this dream.
+     */
     default void onCustomizeClicked() {
     }
 
+    /**
+     * Gets the preview image of this dream.
+     */
     Drawable getPreviewImage();
 
+    /**
+     * Returns whether or not this dream is currently active.
+     */
     boolean isActive();
 
+    /**
+     * Returns whether to allow customization of this dream or not.
+     */
     default boolean allowCustomization() {
         return false;
     }
diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
index 71e65bf..436cde8 100644
--- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
@@ -112,7 +112,8 @@
         if (intent != null && mBatteryListener != null) {
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                 final String batteryLevel = Utils.getBatteryPercentage(intent);
-                final String batteryStatus = Utils.getBatteryStatus(mContext, intent);
+                final String batteryStatus =
+                        Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false);
                 final int batteryHealth = intent.getIntExtra(
                         BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
                 if (!Utils.isBatteryPresent(intent)) {
diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java
index 4e90710..98f19fe 100644
--- a/src/com/android/settings/fuelgauge/BatteryInfo.java
+++ b/src/com/android/settings/fuelgauge/BatteryInfo.java
@@ -37,7 +37,6 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.UsageView;
 import com.android.settingslib.R;
-import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.fuelgauge.EstimateKt;
 import com.android.settingslib.utils.PowerUtil;
@@ -244,6 +243,8 @@
             @NonNull BatteryUsageStats batteryUsageStats, Estimate estimate,
             long elapsedRealtimeUs, boolean shortString) {
         final long startTime = System.currentTimeMillis();
+        final boolean isCompactStatus = context.getResources().getBoolean(
+                com.android.settings.R.bool.config_use_compact_battery_status);
         BatteryInfo info = new BatteryInfo();
         info.mBatteryUsageStats = batteryUsageStats;
         info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
@@ -254,21 +255,21 @@
                 BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)
                 == BatteryManager.BATTERY_HEALTH_OVERHEAT;
 
-        info.statusLabel = getBatteryStatus(context, batteryBroadcast);
+        info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast, isCompactStatus);
         info.batteryStatus = batteryBroadcast.getIntExtra(
                 BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
         if (!info.mCharging) {
             updateBatteryInfoDischarging(context, shortString, estimate, info);
         } else {
             updateBatteryInfoCharging(context, batteryBroadcast, batteryUsageStats,
-                    info);
+                    info, isCompactStatus);
         }
         BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", startTime);
         return info;
     }
 
     private static void updateBatteryInfoCharging(Context context, Intent batteryBroadcast,
-            BatteryUsageStats stats, BatteryInfo info) {
+            BatteryUsageStats stats, BatteryInfo info, boolean compactStatus) {
         final Resources resources = context.getResources();
         final long chargeTimeMs = stats.getChargeTimeRemainingMs();
         final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
@@ -292,7 +293,8 @@
                     R.string.power_remaining_charging_duration_only, timeString);
             info.chargeLabel = context.getString(resId, info.batteryPercentString, timeString);
         } else {
-            final String chargeStatusLabel = getBatteryStatus(context, batteryBroadcast);
+            final String chargeStatusLabel =
+                    Utils.getBatteryStatus(context, batteryBroadcast, compactStatus);
             info.remainingLabel = null;
             info.chargeLabel = info.batteryLevel == 100 ? info.batteryPercentString :
                     resources.getString(R.string.power_charging, info.batteryPercentString,
@@ -326,35 +328,6 @@
         }
     }
 
-    private static String getBatteryStatus(Context context, Intent batteryChangedIntent) {
-        final Resources res = context.getResources();
-        final boolean isShortStatus =
-                res.getBoolean(com.android.settings.R.bool.config_use_compact_battery_status);
-
-        if (!isShortStatus) {
-            return Utils.getBatteryStatus(context, batteryChangedIntent);
-        }
-
-        final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_UNKNOWN);
-        final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
-        String statusString = res.getString(R.string.battery_info_status_unknown);
-
-        if (batteryStatus.isCharged()) {
-            statusString = res.getString(R.string.battery_info_status_full);
-        } else {
-            if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
-                statusString = res.getString(R.string.battery_info_status_charging);
-            } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
-                statusString = res.getString(R.string.battery_info_status_discharging);
-            } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
-                statusString = res.getString(R.string.battery_info_status_not_charging);
-            }
-        }
-
-        return statusString;
-    }
-
     public interface BatteryDataParser {
         void onParsingStarted(long startTime, long endTime);
 
diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java
index c56d89b..051a54a 100644
--- a/src/com/android/settings/homepage/SettingsHomepageActivity.java
+++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java
@@ -32,12 +32,17 @@
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Toolbar;
 
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
@@ -96,7 +101,7 @@
         void onHomepageLoaded();
     }
 
-    private interface FragmentBuilder<T extends Fragment>  {
+    private interface FragmentBuilder<T extends Fragment> {
         T build();
     }
 
@@ -149,7 +154,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setupEdgeToEdge();
         setContentView(R.layout.settings_homepage_container);
+
         mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this);
         mSplitController = SplitController.getInstance();
         mIsTwoPane = mSplitController.isActivityEmbedded(this);
@@ -224,6 +231,24 @@
         }
     }
 
+    private void setupEdgeToEdge() {
+        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+        ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content),
+                (v, windowInsets) -> {
+                    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
+                    // Apply the insets as a margin to the view. Here the system is setting
+                    // only the top dimensions.
+                    ViewGroup.MarginLayoutParams mlp =
+                            (ViewGroup.MarginLayoutParams) v.getLayoutParams();
+                    mlp.topMargin = insets.top;
+                    v.setLayoutParams(mlp);
+
+                    // Return CONSUMED if you don't want the window insets to keep being
+                    // passed down to descendant views.
+                    return WindowInsetsCompat.CONSUMED;
+                });
+    }
+
     private void initSearchBarView() {
         final Toolbar toolbar = findViewById(R.id.search_action_bar);
         FeatureFactory.getFactory(this).getSearchFeatureProvider()
@@ -265,7 +290,7 @@
         // Update status bar color
         window.setStatusBarColor(color);
         // Update content background.
-        findViewById(R.id.settings_homepage_container).setBackgroundColor(color);
+        findViewById(android.R.id.content).setBackgroundColor(color);
     }
 
     private void showSuggestionFragment(boolean scrollNeeded) {
diff --git a/src/com/android/settings/network/EthernetTetherPreferenceController.java b/src/com/android/settings/network/EthernetTetherPreferenceController.java
index 5b2cab7..58c1fd2 100644
--- a/src/com/android/settings/network/EthernetTetherPreferenceController.java
+++ b/src/com/android/settings/network/EthernetTetherPreferenceController.java
@@ -21,52 +21,63 @@
 import android.net.TetheringManager;
 import android.os.Handler;
 import android.os.Looper;
-import android.text.TextUtils;
 
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.OnLifecycleEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.HashSet;
+
 /**
  * This controller helps to manage the switch state and visibility of ethernet tether switch
  * preference.
  */
 public final class EthernetTetherPreferenceController extends TetherBasePreferenceController {
 
-    private final String mEthernetRegex;
+    private final HashSet<String> mAvailableInterfaces = new HashSet<>();
     private final EthernetManager mEthernetManager;
+
     @VisibleForTesting
-    EthernetManager.Listener mEthernetListener;
+    EthernetManager.InterfaceStateListener mEthernetListener;
 
     public EthernetTetherPreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
-        mEthernetRegex = context.getString(
-                com.android.internal.R.string.config_ethernet_iface_regex);
-        mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
+        mEthernetManager = context.getSystemService(EthernetManager.class);
     }
 
     @OnLifecycleEvent(Lifecycle.Event.ON_START)
     public void onStart() {
-        mEthernetListener = (iface, isAvailable) -> updateState(mPreference);
+        mEthernetListener = (iface, state, role, configuration) -> {
+            if (state == EthernetManager.STATE_LINK_UP) {
+                mAvailableInterfaces.add(iface);
+            } else {
+                mAvailableInterfaces.remove(iface);
+            }
+            updateState(mPreference);
+        };
         final Handler handler = new Handler(Looper.getMainLooper());
         // Executor will execute to post the updateState event to a new handler which is created
         // from the main looper when the {@link EthernetManager.Listener.onAvailabilityChanged}
         // is triggerd.
-        mEthernetManager.addListener(mEthernetListener, r -> handler.post(r));
+        if (mEthernetManager != null) {
+            mEthernetManager.addInterfaceStateListener(r -> handler.post(r), mEthernetListener);
+        }
     }
 
     @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
     public void onStop() {
-        mEthernetManager.removeListener(mEthernetListener);
-        mEthernetListener = null;
+        if (mEthernetManager != null) {
+            mEthernetManager.removeInterfaceStateListener(mEthernetListener);
+        }
     }
 
     @Override
     public boolean shouldEnable() {
+        ensureRunningOnMainLoopThread();
         String[] available = mTm.getTetherableIfaces();
         for (String s : available) {
-            if (s.matches(mEthernetRegex)) {
+            if (mAvailableInterfaces.contains(s)) {
                 return true;
             }
         }
@@ -75,11 +86,19 @@
 
     @Override
     public boolean shouldShow() {
-        return !TextUtils.isEmpty(mEthernetRegex);
+        return mEthernetManager != null;
     }
 
     @Override
     public int getTetherType() {
         return TetheringManager.TETHERING_ETHERNET;
     }
+
+    private void ensureRunningOnMainLoopThread() {
+        if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on main loop thread: "
+                            + Thread.currentThread().getName());
+        }
+    }
 }
diff --git a/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
index 0638c60..df59bd5 100644
--- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java
+++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
@@ -29,7 +29,7 @@
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.notification.LockScreenNotificationPreferenceController;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -118,7 +118,7 @@
 
                 @Override
                 protected boolean isPageSearchEnabled(Context context) {
-                    return !SafetyCenterStatusHolder.get().isEnabled(context);
+                    return !SafetyCenterManagerWrapper.get().isEnabled(context);
                 }
             };
 }
diff --git a/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceController.java b/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceController.java
index 87daf7f..b553239 100644
--- a/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceController.java
+++ b/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceController.java
@@ -20,7 +20,7 @@
 import android.content.Context;
 
 import com.android.settings.core.BasePreferenceController;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 
 /** The preference controller for the top level privacy tile. */
 public class TopLevelPrivacyEntryPreferenceController  extends BasePreferenceController {
@@ -31,7 +31,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        if (!SafetyCenterStatusHolder.get().isEnabled(mContext)) {
+        if (!SafetyCenterManagerWrapper.get().isEnabled(mContext)) {
             return AVAILABLE;
         }
         return CONDITIONALLY_UNAVAILABLE;
diff --git a/src/com/android/settings/safetycenter/BiometricsSafetySource.java b/src/com/android/settings/safetycenter/BiometricsSafetySource.java
index f37ea03..bfe2fb0 100644
--- a/src/com/android/settings/safetycenter/BiometricsSafetySource.java
+++ b/src/com/android/settings/safetycenter/BiometricsSafetySource.java
@@ -21,13 +21,13 @@
 /** Combined Biometrics Safety Source for Safety Center. */
 public final class BiometricsSafetySource {
 
-    public static final String SAFETY_SOURCE_ID = "BiometricsSafetySource";
+    public static final String SAFETY_SOURCE_ID = "Biometrics";
 
     private BiometricsSafetySource() {}
 
     /** Sends biometric safety data to Safety Center. */
     public static void sendSafetyData(Context context) {
-        if (!SafetyCenterStatusHolder.get().isEnabled(context)) {
+        if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
             return;
         }
 
diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
index 66001f2..5adaa79 100644
--- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java
+++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
@@ -17,51 +17,76 @@
 package com.android.settings.safetycenter;
 
 import android.app.PendingIntent;
-import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.safetycenter.SafetySourceData;
 import android.safetycenter.SafetySourceStatus;
+import android.safetycenter.SafetySourceStatus.IconAction;
 
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.password.ChooseLockGeneric;
-import com.android.settingslib.transition.SettingsTransitionHelper;
+import com.android.settings.R;
+import com.android.settings.security.ScreenLockPreferenceDetailsUtils;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
 
 /** Lock Screen Safety Source for Safety Center. */
 public final class LockScreenSafetySource {
 
-    public static final String SAFETY_SOURCE_ID = "LockScreenSafetySource";
+    public static final String SAFETY_SOURCE_ID = "LockScreen";
 
-    private LockScreenSafetySource() {}
+    private LockScreenSafetySource() {
+    }
 
     /** Sends lock screen safety data to Safety Center. */
-    public static void sendSafetyData(Context context) {
-        if (!SafetyCenterStatusHolder.get().isEnabled(context)) {
+    public static void sendSafetyData(Context context,
+            ScreenLockPreferenceDetailsUtils screenLockPreferenceDetailsUtils) {
+        if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
             return;
         }
 
-        // TODO(b/215515298): Replace placeholder SafetySourceData with real data.
-        // TODO(b/217409995): Replace SECURITY_ALTERNATIVE with Safety Center metrics category.
-        Intent clickIntent = new SubSettingLauncher(context)
-                .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName())
-                .setSourceMetricsCategory(SettingsEnums.SECURITY_ALTERNATIVE)
-                .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
-                .toIntent();
-        PendingIntent pendingIntent = PendingIntent
+        if (!screenLockPreferenceDetailsUtils.isAvailable()) {
+            return;
+        }
+
+        final int userId = UserHandle.myUserId();
+        final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtilsInternal
+                .checkIfPasswordQualityIsSet(context, userId);
+        final PendingIntent pendingIntent = createPendingIntent(context,
+                screenLockPreferenceDetailsUtils.getLaunchChooseLockGenericFragmentIntent());
+        final IconAction gearMenuIconAction = createGearMenuIconAction(context,
+                screenLockPreferenceDetailsUtils);
+
+        final SafetySourceStatus status = new SafetySourceStatus.Builder(
+                context.getString(R.string.unlock_set_unlock_launch_picker_title),
+                screenLockPreferenceDetailsUtils.getSummary(UserHandle.myUserId()),
+                screenLockPreferenceDetailsUtils.isLockPatternSecure()
+                        ? SafetySourceStatus.STATUS_LEVEL_OK
+                        : SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION,
+                pendingIntent)
+                .setEnabled(
+                        !screenLockPreferenceDetailsUtils.isPasswordQualityManaged(userId, admin))
+                .setIconAction(gearMenuIconAction).build();
+        final SafetySourceData safetySourceData = new SafetySourceData.Builder(
+                SAFETY_SOURCE_ID).setStatus(status).build();
+
+        SafetyCenterManagerWrapper.get().sendSafetyCenterUpdate(context, safetySourceData);
+    }
+
+    private static IconAction createGearMenuIconAction(Context context,
+            ScreenLockPreferenceDetailsUtils screenLockPreferenceDetailsUtils) {
+        return screenLockPreferenceDetailsUtils.shouldShowGearMenu() ? new IconAction(
+                IconAction.ICON_TYPE_GEAR,
+                createPendingIntent(context,
+                        screenLockPreferenceDetailsUtils.getLaunchScreenLockSettingsIntent()))
+                : null;
+    }
+
+    private static PendingIntent createPendingIntent(Context context, Intent intent) {
+        return PendingIntent
                 .getActivity(
                         context,
                         0 /* requestCode */,
-                        clickIntent,
+                        intent,
                         PendingIntent.FLAG_IMMUTABLE);
-        SafetySourceData safetySourceData =
-                new SafetySourceData.Builder(SAFETY_SOURCE_ID).setStatus(
-                        new SafetySourceStatus.Builder(
-                                "Lock Screen",
-                                "Lock screen settings",
-                                SafetySourceStatus.STATUS_LEVEL_OK,
-                                pendingIntent).build()
-                ).build();
-
-        SafetyCenterManagerWrapper.get().sendSafetyCenterUpdate(context, safetySourceData);
     }
 }
diff --git a/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java b/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java
index 7e47f23..e4a1d67 100644
--- a/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java
+++ b/src/com/android/settings/safetycenter/SafetyCenterManagerWrapper.java
@@ -58,4 +58,24 @@
             return;
         }
     }
+
+    /** Returns true is SafetyCenter page is enabled, false otherwise. */
+    public boolean isEnabled(Context context) {
+        if (context == null) {
+            Log.e(TAG, "Context is null at SafetyCenterManagerWrapper#isEnabled");
+            return false;
+        }
+        SafetyCenterManager safetyCenterManager =
+                context.getSystemService(SafetyCenterManager.class);
+        if (safetyCenterManager == null) {
+            Log.w(TAG, "System service SAFETY_CENTER_SERVICE (SafetyCenterManager) is null");
+            return false;
+        }
+        try {
+            return safetyCenterManager.isSafetyCenterEnabled();
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Calling isSafetyCenterEnabled failed.", e);
+            return false;
+        }
+    }
 }
diff --git a/src/com/android/settings/safetycenter/SafetyCenterStatusHolder.java b/src/com/android/settings/safetycenter/SafetyCenterStatusHolder.java
deleted file mode 100644
index 014c600..0000000
--- a/src/com/android/settings/safetycenter/SafetyCenterStatusHolder.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.safetycenter;
-
-import android.content.Context;
-import android.safetycenter.SafetyCenterManager;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/** Knows whether safety center is enabled or disabled. */
-public class SafetyCenterStatusHolder {
-
-    private static final String TAG = "SafetyCenterStatusHolder";
-
-    @VisibleForTesting
-    public static SafetyCenterStatusHolder sInstance;
-
-    private SafetyCenterStatusHolder() {}
-
-    /** Returns an instance of {@link SafetyCenterStatusHolder}. */
-    public static SafetyCenterStatusHolder get() {
-        if (sInstance == null) {
-            sInstance = new SafetyCenterStatusHolder();
-        }
-        return sInstance;
-    }
-
-    /** Returns true is SafetyCenter page is enabled, false otherwise. */
-    public boolean isEnabled(Context context) {
-        if (context == null) {
-            Log.e(TAG, "Context is null at SafetyCenterStatusHolder#isEnabled");
-            return false;
-        }
-        SafetyCenterManager safetyCenterManager =
-                context.getSystemService(SafetyCenterManager.class);
-        if (safetyCenterManager == null) {
-            Log.w(TAG, "System service SAFETY_CENTER_SERVICE (SafetyCenterManager) is null");
-            return false;
-        }
-        try {
-            return safetyCenterManager.isSafetyCenterEnabled();
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Calling isSafetyCenterEnabled failed.", e);
-            return false;
-        }
-    }
-}
diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
index bdc52ad..a0d18c7 100644
--- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
+++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
@@ -20,10 +20,13 @@
 import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
 import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS;
 
+import android.app.settings.SettingsEnums;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 
+import com.android.settings.security.ScreenLockPreferenceDetailsUtils;
+
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
@@ -33,7 +36,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!SafetyCenterStatusHolder.get().isEnabled(context)) {
+        if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
             return;
         }
 
@@ -54,7 +57,8 @@
 
     private static void refreshSafetySources(Context context, List<String> sourceIds) {
         if (sourceIds.contains(LockScreenSafetySource.SAFETY_SOURCE_ID)) {
-            LockScreenSafetySource.sendSafetyData(context);
+            LockScreenSafetySource.sendSafetyData(context,
+                    new ScreenLockPreferenceDetailsUtils(context, SettingsEnums.SAFETY_CENTER));
         }
 
         if (sourceIds.contains(BiometricsSafetySource.SAFETY_SOURCE_ID)) {
@@ -63,7 +67,8 @@
     }
 
     private static void refreshAllSafetySources(Context context) {
-        LockScreenSafetySource.sendSafetyData(context);
+        LockScreenSafetySource.sendSafetyData(context,
+                new ScreenLockPreferenceDetailsUtils(context, SettingsEnums.SAFETY_CENTER));
         BiometricsSafetySource.sendSafetyData(context);
     }
 }
diff --git a/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceController.java b/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceController.java
index b00c084..af9509b 100644
--- a/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceController.java
+++ b/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceController.java
@@ -37,7 +37,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        if (SafetyCenterStatusHolder.get().isEnabled(mContext)) {
+        if (SafetyCenterManagerWrapper.get().isEnabled(mContext)) {
             return AVAILABLE;
         }
         return CONDITIONALLY_UNAVAILABLE;
diff --git a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
index abcacf0..a191acd 100644
--- a/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
+++ b/src/com/android/settings/security/ScreenLockPreferenceDetailsUtils.java
@@ -18,12 +18,14 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.Intent;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 
 import androidx.annotation.StringRes;
 
+import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.R;
 import com.android.settings.Utils;
@@ -82,20 +84,34 @@
     }
 
     /**
+     * Returns whether the lock pattern is secure.
+     */
+    public boolean isLockPatternSecure() {
+        return mLockPatternUtils.isSecure(mUserId);
+    }
+
+    /**
      * Returns whether the Gear Menu should be shown.
      */
     public boolean shouldShowGearMenu() {
-        return mLockPatternUtils.isSecure(mUserId);
+        return isLockPatternSecure();
     }
 
     /**
      * Launches the {@link ScreenLockSettings}.
      */
     public void openScreenLockSettings() {
-        new SubSettingLauncher(mContext)
+        mContext.startActivity(getLaunchScreenLockSettingsIntent());
+    }
+
+    /**
+     * Returns {@link Intent} to launch the {@link ScreenLockSettings}.
+     */
+    public Intent getLaunchScreenLockSettingsIntent() {
+        return new SubSettingLauncher(mContext)
                 .setDestination(ScreenLockSettings.class.getName())
                 .setSourceMetricsCategory(mSourceMetricsCategory)
-                .launch();
+                .toIntent();
     }
 
     /**
@@ -105,6 +121,29 @@
      * @return true if the {@link ChooseLockGenericFragment} is launching.
      */
     public boolean openChooseLockGenericFragment() {
+        final Intent quietModeDialogIntent = getQuietModeDialogIntent();
+        if (quietModeDialogIntent != null) {
+            mContext.startActivity(quietModeDialogIntent);
+            return false;
+        }
+        mContext.startActivity(getChooseLockGenericFragmentIntent());
+        return true;
+    }
+
+    /**
+     * Returns {@link Intent} to launch an appropriate Settings screen.
+     *
+     * <p>If Quiet Mode is enabled for managed profile, returns {@link Intent} to launch a dialog
+     * to disable the Quiet Mode, otherwise returns {@link Intent} to launch
+     * {@link ChooseLockGenericFragment}.
+     */
+    public Intent getLaunchChooseLockGenericFragmentIntent() {
+        final Intent quietModeDialogIntent = getQuietModeDialogIntent();
+        return quietModeDialogIntent != null ? quietModeDialogIntent
+                : getChooseLockGenericFragmentIntent();
+    }
+
+    private Intent getQuietModeDialogIntent() {
         // TODO(b/35930129): Remove once existing password can be passed into vold directly.
         // Currently we need this logic to ensure that the QUIET_MODE is off for any work
         // profile with unified challenge on FBE-enabled devices. Otherwise, vold would not be
@@ -112,17 +151,20 @@
         if (mProfileChallengeUserId != UserHandle.USER_NULL
                 && !mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileChallengeUserId)
                 && StorageManager.isFileEncryptedNativeOnly()) {
-            if (Utils.startQuietModeDialogIfNecessary(mContext, mUm, mProfileChallengeUserId)) {
-                return false;
+            if (mUm.isQuietModeEnabled(UserHandle.of(mProfileChallengeUserId))) {
+                return UnlaunchableAppActivity.createInQuietModeDialogIntent(
+                        mProfileChallengeUserId);
             }
         }
+        return null;
+    }
 
-        new SubSettingLauncher(mContext)
+    private Intent getChooseLockGenericFragmentIntent() {
+        return new SubSettingLauncher(mContext)
                 .setDestination(ChooseLockGenericFragment.class.getName())
                 .setSourceMetricsCategory(mSourceMetricsCategory)
                 .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
-                .launch();
-        return true;
+                .toIntent();
     }
 
     @StringRes
diff --git a/src/com/android/settings/security/SecurityAdvancedSettings.java b/src/com/android/settings/security/SecurityAdvancedSettings.java
index e5f4945..f716064 100644
--- a/src/com/android/settings/security/SecurityAdvancedSettings.java
+++ b/src/com/android/settings/security/SecurityAdvancedSettings.java
@@ -34,7 +34,7 @@
 import com.android.settings.biometrics.fingerprint.FingerprintProfileStatusPreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.security.trustagent.TrustAgentListPreferenceController;
 import com.android.settings.widget.PreferenceCategoryController;
@@ -92,7 +92,7 @@
         final Context context = getContext();
         if (context == null) {
             return CATEGORY_SECURITY_LEGACY_ADVANCED_SETTINGS;
-        } else if (SafetyCenterStatusHolder.get().isEnabled(context)) {
+        } else if (SafetyCenterManagerWrapper.get().isEnabled(context)) {
             return CategoryKey.CATEGORY_SECURITY_ADVANCED_SETTINGS;
         } else {
             final SecuritySettingsFeatureProvider securitySettingsFeatureProvider =
diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java
index 6bfbf6c..6aadee6 100644
--- a/src/com/android/settings/security/SecuritySettings.java
+++ b/src/com/android/settings/security/SecuritySettings.java
@@ -25,7 +25,7 @@
 import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.security.trustagent.TrustAgentListPreferenceController;
 import com.android.settings.widget.PreferenceCategoryController;
@@ -129,7 +129,7 @@
                 protected boolean isPageSearchEnabled(Context context) {
                     return !FeatureFactory.getFactory(context).getSecuritySettingsFeatureProvider()
                             .hasAlternativeSecuritySettingsFragment()
-                            && !SafetyCenterStatusHolder.get().isEnabled(context);
+                            && !SafetyCenterManagerWrapper.get().isEnabled(context);
                 }
             };
 }
diff --git a/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java b/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java
index 11d1dc3..3cf40a0 100644
--- a/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java
+++ b/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java
@@ -24,7 +24,7 @@
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 
 public class TopLevelSecurityEntryPreferenceController extends BasePreferenceController {
 
@@ -38,7 +38,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        if (!SafetyCenterStatusHolder.get().isEnabled(mContext)) {
+        if (!SafetyCenterManagerWrapper.get().isEnabled(mContext)) {
             return AVAILABLE;
         }
         return CONDITIONALLY_UNAVAILABLE;
diff --git a/tests/robotests/src/com/android/settings/AllInOneTetherSettingsTest.java b/tests/robotests/src/com/android/settings/AllInOneTetherSettingsTest.java
index aec31fc..01ec42e 100644
--- a/tests/robotests/src/com/android/settings/AllInOneTetherSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/AllInOneTetherSettingsTest.java
@@ -33,6 +33,7 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.EthernetManager;
 import android.net.TetheringManager;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiManager;
@@ -84,6 +85,8 @@
     private PreferenceScreen mPreferenceScreen;
     @Mock
     private PreferenceGroup mWifiTetherGroup;
+    @Mock
+    private EthernetManager mEthernetManager;
 
     @Before
     public void setUp() {
@@ -95,6 +98,7 @@
                 .when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE);
         doReturn(mTetheringManager)
                 .when(mContext).getSystemService(Context.TETHERING_SERVICE);
+        doReturn(mEthernetManager).when(mContext).getSystemService(EthernetManager.class);
         doReturn(WIFI_REGEXS).when(mTetheringManager).getTetherableWifiRegexs();
         doReturn(USB_REGEXS).when(mTetheringManager).getTetherableUsbRegexs();
         doReturn(BT_REGEXS).when(mTetheringManager).getTetherableBluetoothRegexs();
diff --git a/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java
index ef8f569..f908b8a 100644
--- a/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/LockScreenRotationPreferenceControllerTest.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.view.RotationPolicy;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager;
 import com.android.settings.testutils.shadow.ShadowRotationPolicy;
 
 import org.junit.Before;
@@ -50,7 +51,10 @@
     }
 
     @Test
-    @Config(shadows = {ShadowRotationPolicy.class})
+    @Config(shadows = {
+            ShadowRotationPolicy.class,
+            ShadowDeviceStateRotationLockSettingsManager.class
+    })
     public void getAvailabilityStatus_supportedRotation_shouldReturnAvailable() {
         ShadowRotationPolicy.setRotationSupported(true /* supported */);
 
@@ -59,8 +63,23 @@
     }
 
     @Test
-    @Config(shadows = {ShadowRotationPolicy.class})
-    public void getAvailabilityStatus_unsupportedRotation_shouldReturnUnsupportedOnDevice() {
+    @Config(shadows = {
+            ShadowRotationPolicy.class,
+            ShadowDeviceStateRotationLockSettingsManager.class
+    })
+    public void getAvailabilityStatus_deviceStateRotationEnabled_returnsUnsupported() {
+        ShadowRotationPolicy.setRotationSupported(true /* supported */);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                BasePreferenceController.UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    @Config(shadows = {
+            ShadowRotationPolicy.class,
+            ShadowDeviceStateRotationLockSettingsManager.class
+    })    public void getAvailabilityStatus_unsupportedRotation_shouldReturnUnsupportedOnDevice() {
         ShadowRotationPolicy.setRotationSupported(false /* supported */);
 
         assertThat(mController.getAvailabilityStatus()).isEqualTo(
diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java
index 00cf052..c9bb687 100644
--- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java
@@ -18,10 +18,7 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -42,7 +39,6 @@
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -96,7 +92,6 @@
                 mContext, TEST_PREF_KEY, mLifecycle);
     }
 
-    @Ignore
     @Test
     public void updateState_parentalConsentRequired_preferenceDisabled() {
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
@@ -106,16 +101,10 @@
         RestrictedLockUtils.EnforcedAdmin admin = mock(RestrictedLockUtils.EnforcedAdmin.class);
 
         mController.mPreference = restrictedPreference;
-        mController.updateStateInternal(admin, true, true);
+        mController.updateStateInternal(admin);
         verify(restrictedPreference).setDisabledByAdmin(eq(admin));
 
-        mController.updateStateInternal(admin, true, false);
-        verify(restrictedPreference).setDisabledByAdmin(eq(null));
-
-        mController.updateStateInternal(admin, false, true);
-        verify(restrictedPreference).setDisabledByAdmin(eq(null));
-
-        mController.updateStateInternal(admin, false, false);
+        mController.updateStateInternal(null);
         verify(restrictedPreference).setDisabledByAdmin(eq(null));
     }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
index 6087ef2..51cad70 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
@@ -387,34 +387,52 @@
     }
 
     @Test
-    public void showBatteryPredictionIfNecessary_estimateReadyIsAvailable_showView() {
-        mController.showBatteryPredictionIfNecessary(1, 14218009,
-                mLayoutPreference.findViewById(R.id.layout_left));
-        mController.showBatteryPredictionIfNecessary(1, 14218009,
-                mLayoutPreference.findViewById(R.id.layout_middle));
-        mController.showBatteryPredictionIfNecessary(1, 14218009,
-                mLayoutPreference.findViewById(R.id.layout_right));
+    public void estimateReadyIsBothAvailable_showsView() {
+        mController.mIsLeftDeviceEstimateReady = true;
+        mController.mIsRightDeviceEstimateReady = true;
+
+        mController.showBothDevicesBatteryPredictionIfNecessary();
 
         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
                 View.VISIBLE);
-        assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_middle),
-                View.VISIBLE);
         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
                 View.VISIBLE);
     }
 
     @Test
-    public void showBatteryPredictionIfNecessary_estimateReadyIsNotAvailable_notShowView() {
-        mController.showBatteryPredictionIfNecessary(0, 14218009,
-                mLayoutPreference.findViewById(R.id.layout_left));
-        mController.showBatteryPredictionIfNecessary(0, 14218009,
-                mLayoutPreference.findViewById(R.id.layout_middle));
-        mController.showBatteryPredictionIfNecessary(0, 14218009,
-                mLayoutPreference.findViewById(R.id.layout_right));
+    public void leftDeviceEstimateIsReadyRightDeviceIsNotReady_notShowView() {
+        mController.mIsLeftDeviceEstimateReady = true;
+        mController.mIsRightDeviceEstimateReady = false;
+
+        mController.showBothDevicesBatteryPredictionIfNecessary();
 
         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
                 View.GONE);
-        assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_middle),
+        assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
+                View.GONE);
+    }
+
+    @Test
+    public void leftDeviceEstimateIsNotReadyRightDeviceIsReady_notShowView() {
+        mController.mIsLeftDeviceEstimateReady = false;
+        mController.mIsRightDeviceEstimateReady = true;
+
+        mController.showBothDevicesBatteryPredictionIfNecessary();
+
+        assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
+                View.GONE);
+        assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
+                View.GONE);
+    }
+
+    @Test
+    public void bothDevicesEstimateIsNotReady_notShowView() {
+        mController.mIsLeftDeviceEstimateReady = false;
+        mController.mIsRightDeviceEstimateReady = false;
+
+        mController.showBothDevicesBatteryPredictionIfNecessary();
+
+        assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
                 View.GONE);
         assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
                 View.GONE);
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
index fd1c8ff..aa5f980 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
@@ -144,6 +144,21 @@
     }
 
     @Test
+    public void useAll_returnsAllControllersOfType() {
+        final TestPreferenceController controller1 = new TestPreferenceController(mContext);
+        final TestPreferenceController controller2 = new TestPreferenceController(mContext);
+        final SubTestPreferenceController controller3 = new SubTestPreferenceController(mContext);
+        mTestFragment.addPreferenceController(controller1);
+        mTestFragment.addPreferenceController(controller2);
+        mTestFragment.addPreferenceController(controller3);
+
+        final List<TestPreferenceController> retrievedControllers = mTestFragment.useAll(
+                TestPreferenceController.class);
+
+        assertThat(retrievedControllers).containsExactly(controller1, controller2);
+    }
+
+    @Test
     public void displayTilesAsPreference_shouldAddTilesWithIntent() {
         when(mFakeFeatureFactory.dashboardFeatureProvider
                 .getTilesForCategory(nullable(String.class)))
@@ -360,6 +375,13 @@
         }
     }
 
+    public static class SubTestPreferenceController extends TestPreferenceController {
+
+        private SubTestPreferenceController(Context context) {
+            super(context);
+        }
+    }
+
     private static class TestFragment extends DashboardFragment {
 
         private final PreferenceManager mPreferenceManager;
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java
new file mode 100644
index 0000000..1956b09
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageCacheHelperTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.deviceinfo.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class StorageCacheHelperTest {
+    private static final long FAKE_IMAGES_SIZE = 7000L;
+    private static final long FAKE_VIDEOS_SIZE = 8900L;
+    private static final long FAKE_AUDIO_SIZE = 3500L;
+    private static final long FAKE_APPS_SIZE = 4000L;
+    private static final long FAKE_GAMES_SIZE = 5000L;
+    private static final long FAKE_DOCS_SIZE = 1500L;
+    private static final long FAKE_TRASH_SIZE = 500L;
+    private static final long FAKE_SYSTEM_SIZE = 2300L;
+    private static final long FAKE_TOTAL_SIZE = 256000L;
+    private static final long FAKE_USED_SIZE = 50000L;
+
+    private Context mContext;
+    private StorageCacheHelper mHelper;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mHelper = new StorageCacheHelper(mContext, UserHandle.myUserId());
+    }
+
+    @Test
+    public void hasCachedSizeInfo_noCacheData_shouldReturnFalse() {
+        assertThat(mHelper.hasCachedSizeInfo()).isFalse();
+    }
+
+    @Test
+    public void hasCachedSizeInfo_hasCacheData_shouldReturnTrue() {
+        mHelper.cacheSizeInfo(getFakeStorageCache());
+
+        assertThat(mHelper.hasCachedSizeInfo()).isTrue();
+    }
+
+    @Test
+    public void cacheSizeInfo_shouldSaveToSharedPreference() {
+        mHelper.cacheSizeInfo(getFakeStorageCache());
+
+        StorageCacheHelper.StorageCache storageCache = mHelper.retrieveCachedSize();
+
+        assertThat(storageCache.imagesSize).isEqualTo(FAKE_IMAGES_SIZE);
+        assertThat(storageCache.totalSize).isEqualTo(0);
+    }
+
+    @Test
+    public void cacheTotalSizeAndUsedSize_shouldSaveToSharedPreference() {
+        mHelper.cacheTotalSizeAndUsedSize(FAKE_TOTAL_SIZE, FAKE_USED_SIZE);
+
+        StorageCacheHelper.StorageCache storageCache = mHelper.retrieveCachedSize();
+
+        assertThat(storageCache.totalSize).isEqualTo(FAKE_TOTAL_SIZE);
+        assertThat(storageCache.usedSize).isEqualTo(FAKE_USED_SIZE);
+    }
+
+    private StorageCacheHelper.StorageCache getFakeStorageCache() {
+        StorageCacheHelper.StorageCache result = new StorageCacheHelper.StorageCache();
+        result.trashSize = FAKE_TRASH_SIZE;
+        result.systemSize = FAKE_SYSTEM_SIZE;
+        result.imagesSize = FAKE_IMAGES_SIZE;
+        result.documentsAndOtherSize = FAKE_DOCS_SIZE;
+        result.audioSize = FAKE_AUDIO_SIZE;
+        result.gamesSize = FAKE_GAMES_SIZE;
+        result.videosSize = FAKE_VIDEOS_SIZE;
+        result.allAppsExceptGamesSize = FAKE_APPS_SIZE;
+        return result;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/AutoRotatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AutoRotatePreferenceControllerTest.java
index 1d175de..54e6b99 100644
--- a/tests/robotests/src/com/android/settings/display/AutoRotatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/AutoRotatePreferenceControllerTest.java
@@ -30,6 +30,7 @@
 
 import androidx.preference.SwitchPreference;
 
+import com.android.internal.R;
 import com.android.internal.view.RotationPolicy;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -64,6 +65,7 @@
         mPreference = new SwitchPreference(RuntimeEnvironment.application);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        disableDeviceStateRotation();
 
         mController = new AutoRotatePreferenceController(mContext, "auto_rotate");
     }
@@ -112,6 +114,26 @@
     }
 
     @Test
+    public void getAvailabilityStatus_deviceRotationDisabled_returnsAvailable() {
+        enableAutoRotationPreference();
+        disableDeviceStateRotation();
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(BasePreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_deviceRotationEnabled_returnsUnsupported() {
+        enableAutoRotationPreference();
+        enableDeviceStateRotation();
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
     public void testIsCheck() {
         assertThat(mController.isChecked()).isFalse();
 
@@ -180,4 +202,15 @@
         Settings.System.putIntForUser(mContentResolver,
                 Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
     }
+
+    private void enableDeviceStateRotation() {
+        when(mContext.getResources().getStringArray(
+                R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+                new String[]{"0:0", "1:1", "2:2"});
+    }
+
+    private void disableDeviceStateRotation() {
+        when(mContext.getResources().getStringArray(
+                R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(new String[]{});
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java
new file mode 100644
index 0000000..b773657
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateDetailsFragmentTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class DeviceStateAutoRotateDetailsFragmentTest {
+
+    private final DeviceStateAutoRotateDetailsFragment mFragment =
+            spy(new DeviceStateAutoRotateDetailsFragment());
+    private final Context mContext = spy(RuntimeEnvironment.application);
+    private final Resources mResources = spy(mContext.getResources());
+
+    @Before
+    public void setUp() throws Exception {
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mFragment.getContext()).thenReturn(mContext);
+        when(mFragment.getResources()).thenReturn(mResources);
+    }
+
+    @Test
+    public void getMetricsCategory_returnsAutoRotateSettings() {
+        assertThat(mFragment.getMetricsCategory()).isEqualTo(
+                SettingsEnums.DISPLAY_AUTO_ROTATE_SETTINGS);
+    }
+
+    @Test
+    public void getPreferenceScreenResId_returnsDeviceStateAutoRotationSettings() {
+        assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
+                R.xml.device_state_auto_rotate_settings);
+    }
+
+    @Test
+    public void createPreferenceControllers_settableDeviceStates_returnsDeviceStateControllers() {
+        enableDeviceStateSettableRotationStates(new String[]{"0:1", "1:1"},
+                new String[]{"Folded", "Unfolded"});
+
+        List<AbstractPreferenceController> preferenceControllers =
+                mFragment.createPreferenceControllers(mContext);
+
+        assertThat(preferenceControllers).hasSize(2);
+        assertThat(preferenceControllers.get(0)).isInstanceOf(
+                DeviceStateAutoRotateSettingController.class);
+        assertThat(preferenceControllers.get(1)).isInstanceOf(
+                DeviceStateAutoRotateSettingController.class);
+    }
+
+    @Test
+    public void createPreferenceControllers_noSettableDeviceStates_returnsEmptyList() {
+        enableDeviceStateSettableRotationStates(new String[]{}, new String[]{});
+
+        List<AbstractPreferenceController> preferenceControllers =
+                mFragment.createPreferenceControllers(mContext);
+
+        assertThat(preferenceControllers).isEmpty();
+    }
+
+    private void enableDeviceStateSettableRotationStates(String[] settableStates,
+            String[] settableStatesDescriptions) {
+        when(mResources.getStringArray(
+                com.android.internal.R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+                settableStates);
+        when(mResources.getStringArray(
+                R.array.config_settableAutoRotationDeviceStatesDescriptions)).thenReturn(
+                settableStatesDescriptions);
+        DeviceStateRotationLockSettingsManager.resetInstance();
+        DeviceStateRotationLockSettingsManager.getInstance(mContext)
+                .resetStateForTesting(mResources);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java
new file mode 100644
index 0000000..a5416e7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateOverviewControllerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.display;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager;
+import com.android.settings.testutils.shadow.ShadowRotationPolicy;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowRotationPolicy.class, ShadowDeviceStateRotationLockSettingsManager.class})
+public class DeviceStateAutoRotateOverviewControllerTest {
+
+    private final DeviceStateAutoRotateOverviewController mController =
+            new DeviceStateAutoRotateOverviewController(
+                    RuntimeEnvironment.application, "device_state_auto_rotate");
+
+    @Test
+    public void getAvailabilityStatus_rotationAndDeviceStateRotationEnabled_returnsAvailable() {
+        ShadowRotationPolicy.setRotationSupported(true);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_rotationNotSupported_returnsUnsupportedOnDevice() {
+        ShadowRotationPolicy.setRotationSupported(false);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_deviceStateRotationNotSupported_returnsUnsupportedOnDevice() {
+        ShadowRotationPolicy.setRotationSupported(true);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false);
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java
new file mode 100644
index 0000000..28a071a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/DeviceStateAutoRotateSettingControllerTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.display;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager;
+import com.android.settings.testutils.shadow.ShadowRotationPolicy;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowRotationPolicy.class,
+        ShadowDeviceStateRotationLockSettingsManager.class
+})
+public class DeviceStateAutoRotateSettingControllerTest {
+
+    private static final int DEFAULT_DEVICE_STATE = 1;
+    private static final String DEFAULT_DEVICE_STATE_DESCRIPTION = "Device state description";
+    private static final int DEFAULT_ORDER = -10;
+
+    private final Context mContext = RuntimeEnvironment.application;
+    private final DeviceStateAutoRotateSettingController mController =
+            new DeviceStateAutoRotateSettingController(mContext, DEFAULT_DEVICE_STATE,
+                    DEFAULT_DEVICE_STATE_DESCRIPTION, DEFAULT_ORDER);
+    private final DeviceStateRotationLockSettingsManager mAutoRotateSettingsManager =
+            DeviceStateRotationLockSettingsManager.getInstance(mContext);
+
+    @Test
+    public void displayPreference_addsPreferenceToPreferenceScreen() {
+        PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext);
+
+        mController.displayPreference(screen);
+
+        assertThat(screen.getPreferenceCount()).isEqualTo(1);
+        Preference preference = screen.getPreference(0);
+        assertThat(preference.getTitle().toString()).isEqualTo(DEFAULT_DEVICE_STATE_DESCRIPTION);
+        assertThat(preference.getOrder()).isEqualTo(DEFAULT_ORDER);
+        assertThat(preference.getKey()).isEqualTo(mController.getPreferenceKey());
+    }
+
+    @Test
+    public void getAvailabilityStatus_rotationAndDeviceStateRotationEnabled_returnsAvailable() {
+        ShadowRotationPolicy.setRotationSupported(true);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_deviceStateRotationDisabled_returnsUnsupported() {
+        ShadowRotationPolicy.setRotationSupported(true);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false);
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_rotationDisabled_returnsUnsupported() {
+        ShadowRotationPolicy.setRotationSupported(false);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        int availability = mController.getAvailabilityStatus();
+
+        assertThat(availability).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getPreferenceKey_returnsKeyBasedOnDeviceState() {
+        String key = mController.getPreferenceKey();
+
+        String expectedKey = "auto_rotate_device_state_" + DEFAULT_DEVICE_STATE;
+        assertThat(key).isEqualTo(expectedKey);
+    }
+
+    @Test
+    public void isChecked_settingForStateIsUnlocked_returnsTrue() {
+        mAutoRotateSettingsManager.updateSetting(DEFAULT_DEVICE_STATE, /* rotationLocked= */ false);
+
+        assertThat(mController.isChecked()).isTrue();
+    }
+
+    @Test
+    public void isChecked_settingForStateIsLocked_returnsFalse() {
+        mAutoRotateSettingsManager.updateSetting(DEFAULT_DEVICE_STATE, /* rotationLocked= */ true);
+
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    @Test
+    public void setChecked_true_deviceStateSettingIsUnlocked() {
+        mController.setChecked(true);
+
+        boolean rotationLocked = mAutoRotateSettingsManager.isRotationLocked(DEFAULT_DEVICE_STATE);
+
+        assertThat(rotationLocked).isFalse();
+    }
+
+    @Test
+    public void setChecked_false_deviceStateSettingIsLocked() {
+        mController.setChecked(false);
+
+        boolean rotationLocked = mAutoRotateSettingsManager.isRotationLocked(DEFAULT_DEVICE_STATE);
+
+        assertThat(rotationLocked).isTrue();
+    }
+
+    @Test
+    public void updateRawDataToIndex_addsItemToList() {
+        List<SearchIndexableRaw> rawData = new ArrayList<>();
+
+        mController.updateRawDataToIndex(rawData);
+
+        assertThat(rawData).hasSize(1);
+        SearchIndexableRaw item = rawData.get(0);
+        assertThat(item.key).isEqualTo(mController.getPreferenceKey());
+        assertThat(item.title).isEqualTo(DEFAULT_DEVICE_STATE_DESCRIPTION);
+        assertThat(item.screenTitle).isEqualTo(mContext.getString(R.string.accelerometer_title));
+    }
+
+    @Test
+    public void getSliceHighlightMenuRes_returnsMenuKeyDisplay() {
+        int sliceHighlightMenuRes = mController.getSliceHighlightMenuRes();
+
+        assertThat(sliceHighlightMenuRes).isEqualTo(R.string.menu_key_display);
+    }
+
+    @Test
+    public void isSliceable_returnsTrue() {
+        assertThat(mController.isSliceable()).isTrue();
+    }
+
+    @Test
+    public void isPublicSlice_returnsTrue() {
+        assertThat(mController.isPublicSlice()).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java
index 778721a..4fec38b 100644
--- a/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotateControllerTest.java
@@ -39,7 +39,10 @@
 import androidx.preference.Preference;
 
 import com.android.settings.testutils.ResolveInfoBuilder;
+import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager;
+import com.android.settings.testutils.shadow.ShadowRotationPolicy;
 import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,6 +66,8 @@
     @Mock
     private Preference mPreference;
     private ContentResolver mContentResolver;
+    private final DeviceStateRotationLockSettingsManager mDeviceStateAutoRotateSettingsManager =
+            DeviceStateRotationLockSettingsManager.getInstance(RuntimeEnvironment.application);
 
     @Before
     public void setUp() {
@@ -122,6 +127,34 @@
         assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
     }
 
+    @Test
+    @Config(shadows = {
+            ShadowDeviceStateRotationLockSettingsManager.class,
+            ShadowRotationPolicy.class
+    })
+    public void getAvailabilityStatus_deviceStateRotationLocked_returnDisableDependentSetting() {
+        enableDeviceStateRotation();
+        lockDeviceStateRotation();
+
+        int availabilityStatus = mController.getAvailabilityStatus();
+
+        assertThat(availabilityStatus).isEqualTo(DISABLED_DEPENDENT_SETTING);
+    }
+
+    @Test
+    @Config(shadows = {
+            ShadowDeviceStateRotationLockSettingsManager.class,
+            ShadowRotationPolicy.class
+    })
+    public void getAvailabilityStatus_deviceStateRotationUnlocked_returnAvailable() {
+        enableDeviceStateRotation();
+        unlockDeviceStateRotation();
+
+        int availabilityStatus = mController.getAvailabilityStatus();
+
+        assertThat(availabilityStatus).isEqualTo(AVAILABLE);
+    }
+
     private void enableAutoRotation() {
         Settings.System.putIntForUser(mContentResolver,
                 Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
@@ -131,4 +164,23 @@
         Settings.System.putIntForUser(mContentResolver,
                 Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
     }
+
+    private void enableDeviceStateRotation() {
+        ShadowRotationPolicy.setRotationSupported(true);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+    }
+
+    private void lockDeviceStateRotation() {
+        mDeviceStateAutoRotateSettingsManager.updateSetting(
+                /* deviceState= */0, /* rotationLocked= */ true);
+        mDeviceStateAutoRotateSettingsManager.updateSetting(
+                /* deviceState= */1, /* rotationLocked= */ true);
+    }
+
+    private void unlockDeviceStateRotation() {
+        mDeviceStateAutoRotateSettingsManager.updateSetting(
+                /* deviceState= */0, /* rotationLocked= */ false);
+        mDeviceStateAutoRotateSettingsManager.updateSetting(
+                /* deviceState= */1, /* rotationLocked= */ true);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java
index 068de34..39fdb04 100644
--- a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceControllerTest.java
@@ -40,6 +40,7 @@
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.ResolveInfoBuilder;
+import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager;
 import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager;
 
 import org.junit.Before;
@@ -53,7 +54,10 @@
 import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowSensorPrivacyManager.class)
+@Config(shadows = {
+        ShadowSensorPrivacyManager.class,
+        ShadowDeviceStateRotationLockSettingsManager.class
+})
 public class SmartAutoRotatePreferenceControllerTest {
 
     private static final String PACKAGE_NAME = "package_name";
@@ -95,6 +99,7 @@
                 new SmartAutoRotatePreferenceController(mContext, "smart_auto_rotate"));
         when(mController.isCameraLocked()).thenReturn(false);
         when(mController.isPowerSaveMode()).thenReturn(false);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false);
     }
 
     @Test
@@ -199,6 +204,16 @@
                 .UNSUPPORTED_ON_DEVICE);
     }
 
+
+    @Test
+    public void getAvailabilityStatus_deviceStateRotationEnabled_returnsUnsupported() {
+        enableAutoRotationPreference();
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(
+                BasePreferenceController.UNSUPPORTED_ON_DEVICE);
+    }
+
     @Test
     public void isSliceableCorrectKey_returnsTrue() {
         final AutoRotatePreferenceController controller =
diff --git a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java
index 877d2c1..942fed6 100644
--- a/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/display/SmartAutoRotatePreferenceFragmentTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.settings.display.SmartAutoRotatePreferenceFragment.AUTO_ROTATE_SWITCH_PREFERENCE_ID;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -33,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.view.View;
 
 import androidx.preference.Preference;
@@ -40,7 +43,11 @@
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.testutils.ResolveInfoBuilder;
+import com.android.settings.testutils.shadow.ShadowDeviceStateRotationLockSettingsManager;
+import com.android.settings.testutils.shadow.ShadowRotationPolicy;
 import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,8 +56,15 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowDeviceStateRotationLockSettingsManager.class,
+        ShadowRotationPolicy.class
+})
 public class SmartAutoRotatePreferenceFragmentTest {
 
     private static final String PACKAGE_NAME = "package_name";
@@ -70,19 +84,24 @@
 
     @Mock
     private Preference mRotateSwitchPreference;
+    private Resources mResources;
+    private Context mContext;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        final Context context = spy(RuntimeEnvironment.application);
+        mContext = spy(RuntimeEnvironment.application);
         ContentResolver mContentResolver = RuntimeEnvironment.application.getContentResolver();
-        when(context.getPackageManager()).thenReturn(mPackageManager);
-        when(context.getContentResolver()).thenReturn(mContentResolver);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
         doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
         doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
                 Manifest.permission.CAMERA, PACKAGE_NAME);
 
+        mResources = spy(mContext.getResources());
+        when(mContext.getResources()).thenReturn(mResources);
+
         final ResolveInfo resolveInfo = new ResolveInfoBuilder(PACKAGE_NAME).build();
         resolveInfo.serviceInfo = new ServiceInfo();
         when(mPackageManager.resolveService(any(), anyInt())).thenReturn(resolveInfo);
@@ -90,15 +109,16 @@
         mFragment = spy(new SmartAutoRotatePreferenceFragment());
         when(mActivity.getPackageManager()).thenReturn(mPackageManager);
         when(mFragment.getActivity()).thenReturn(mActivity);
-        when(mFragment.getContext()).thenReturn(context);
+        when(mFragment.getContext()).thenReturn(mContext);
         doReturn(mView).when(mFragment).getView();
 
         when(mFragment.findPreference(AUTO_ROTATE_SWITCH_PREFERENCE_ID)).thenReturn(
                 mRotateSwitchPreference);
 
-        mSwitchBar = spy(new SettingsMainSwitchBar(context));
+        mSwitchBar = spy(new SettingsMainSwitchBar(mContext));
         when(mActivity.getSwitchBar()).thenReturn(mSwitchBar);
         doReturn(mSwitchBar).when(mView).findViewById(R.id.switch_bar);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(false);
     }
 
 
@@ -111,6 +131,17 @@
     }
 
     @Test
+    public void createHeader_deviceStateRotationSupported_switchBarIsDisabled() {
+        ShadowRotationPolicy.setRotationSupported(true);
+        ShadowDeviceStateRotationLockSettingsManager.setDeviceStateRotationLockEnabled(true);
+
+        mFragment.createHeader(mActivity);
+
+        verify(mSwitchBar, never()).show();
+        verify(mRotateSwitchPreference, never()).setVisible(false);
+    }
+
+    @Test
     public void createHeader_faceDetectionUnSupported_switchBarIsDisabled() {
         doReturn(null).when(mPackageManager).getRotationResolverPackageName();
 
@@ -120,4 +151,41 @@
         verify(mRotateSwitchPreference, never()).setVisible(false);
     }
 
+    @Test
+    public void createPreferenceControllers_noSettableDeviceStates_returnsEmptyList() {
+        enableDeviceStateSettableRotationStates(new String[]{}, new String[]{});
+
+        List<AbstractPreferenceController> preferenceControllers =
+                mFragment.createPreferenceControllers(mContext);
+
+        assertThat(preferenceControllers).isEmpty();
+    }
+
+    @Test
+    public void createPreferenceControllers_settableDeviceStates_returnsDeviceStateControllers() {
+        enableDeviceStateSettableRotationStates(new String[]{"0:1", "1:1"},
+                new String[]{"Folded", "Unfolded"});
+
+        List<AbstractPreferenceController> preferenceControllers =
+                mFragment.createPreferenceControllers(mContext);
+
+        assertThat(preferenceControllers).hasSize(2);
+        assertThat(preferenceControllers.get(0)).isInstanceOf(
+                DeviceStateAutoRotateSettingController.class);
+        assertThat(preferenceControllers.get(1)).isInstanceOf(
+                DeviceStateAutoRotateSettingController.class);
+    }
+
+    private void enableDeviceStateSettableRotationStates(String[] settableStates,
+            String[] settableStatesDescriptions) {
+        when(mResources.getStringArray(
+                com.android.internal.R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+                settableStates);
+        when(mResources.getStringArray(
+                R.array.config_settableAutoRotationDeviceStatesDescriptions)).thenReturn(
+                settableStatesDescriptions);
+        DeviceStateRotationLockSettingsManager.resetInstance();
+        DeviceStateRotationLockSettingsManager.getInstance(mContext)
+                .resetStateForTesting(mResources);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
index 5f08698..d446930 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
@@ -85,8 +85,8 @@
 
         assertThat(mBatteryBroadcastReceiver.mBatteryLevel)
                 .isEqualTo(Utils.getBatteryPercentage(mChargingIntent));
-        assertThat(mBatteryBroadcastReceiver.mBatteryStatus)
-                .isEqualTo(Utils.getBatteryStatus(mContext, mChargingIntent));
+        assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(
+                Utils.getBatteryStatus(mContext, mChargingIntent, /* compactStatus= */ false));
         verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL);
     }
 
@@ -134,7 +134,7 @@
     public void testOnReceive_batteryDataNotChanged_listenerNotInvoked() {
         final String batteryLevel = Utils.getBatteryPercentage(mChargingIntent);
         final String batteryStatus =
-                Utils.getBatteryStatus(mContext, mChargingIntent);
+                Utils.getBatteryStatus(mContext, mChargingIntent, /* compactStatus= */ false);
         mBatteryBroadcastReceiver.mBatteryLevel = batteryLevel;
         mBatteryBroadcastReceiver.mBatteryStatus = batteryStatus;
 
@@ -159,8 +159,8 @@
 
         assertThat(mBatteryBroadcastReceiver.mBatteryLevel)
                 .isEqualTo(Utils.getBatteryPercentage(mChargingIntent));
-        assertThat(mBatteryBroadcastReceiver.mBatteryStatus)
-                .isEqualTo(Utils.getBatteryStatus(mContext, mChargingIntent));
+        assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(
+                Utils.getBatteryStatus(mContext, mChargingIntent, /* compactStatus= */ false));
         assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
                 .isEqualTo(BatteryManager.BATTERY_HEALTH_UNKNOWN);
         // 2 times because register will force update the battery
diff --git a/tests/robotests/src/com/android/settings/network/EthernetTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/EthernetTetherPreferenceControllerTest.java
index bf4811b..68d80d4 100644
--- a/tests/robotests/src/com/android/settings/network/EthernetTetherPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/EthernetTetherPreferenceControllerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -65,10 +66,9 @@
         mPreference = spy(SwitchPreference.class);
         when(mContext.getSystemService(Context.TETHERING_SERVICE)).thenReturn(mTetheringManager);
         when(mTetheringManager.getTetherableIfaces()).thenReturn(new String[]{ETHERNET_REGEX});
-        when(mContext.getSystemService(Context.ETHERNET_SERVICE)).thenReturn(mEthernetManager);
+        when(mContext.getSystemService(EthernetManager.class)).thenReturn(mEthernetManager);
         mController = new EthernetTetherPreferenceController(mContext, "ethernet");
         mController.setTetherEnabler(mTetherEnabler);
-        ReflectionHelpers.setField(mController, "mEthernetRegex", ETHERNET_REGEX);
         ReflectionHelpers.setField(mController, "mPreference", mPreference);
     }
 
@@ -77,7 +77,8 @@
     public void lifecycle_shouldRegisterReceiverOnStart() {
         mController.onStart();
 
-        verify(mEthernetManager).addListener(eq(mController.mEthernetListener));
+        verify(mEthernetManager).addInterfaceStateListener(any(),
+                eq(mController.mEthernetListener));
     }
 
     @Test
@@ -95,11 +96,10 @@
     @Test
     public void lifecycle_shouldUnregisterReceiverOnStop() {
         mController.onStart();
-        EthernetManager.Listener listener = mController.mEthernetListener;
+        EthernetManager.InterfaceStateListener listener = mController.mEthernetListener;
         mController.onStop();
 
-        verify(mEthernetManager).removeListener(eq(listener));
-        assertThat(mController.mEthernetListener).isNull();
+        verify(mEthernetManager).removeInterfaceStateListener(eq(listener));
     }
 
     @Test
@@ -110,8 +110,11 @@
 
     @Test
     public void shouldShow_noEthernetInterface() {
-        ReflectionHelpers.setField(mController, "mEthernetRegex", "");
-        assertThat(mController.shouldShow()).isFalse();
+        when(mContext.getSystemService(EthernetManager.class)).thenReturn(null);
+
+        final EthernetTetherPreferenceController controller =
+                new EthernetTetherPreferenceController(mContext, "ethernet");
+        assertThat(controller.shouldShow()).isFalse();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateRotationLockSettingsManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateRotationLockSettingsManager.java
new file mode 100644
index 0000000..72df3cc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowDeviceStateRotationLockSettingsManager.java
@@ -0,0 +1,39 @@
+/*
+ * 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.testutils.shadow;
+
+import android.content.Context;
+
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(DeviceStateRotationLockSettingsManager.class)
+public class ShadowDeviceStateRotationLockSettingsManager {
+
+    private static boolean sDeviceStateRotationLockEnabled;
+
+    @Implementation
+    public static boolean isDeviceStateRotationLockEnabled(Context context) {
+        return sDeviceStateRotationLockEnabled;
+    }
+
+    public static void setDeviceStateRotationLockEnabled(boolean enabled) {
+        sDeviceStateRotationLockEnabled = enabled;
+    }
+}
diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceControllerTest.java
index 90b8927..38c605c 100644
--- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/BridgedAppsPreferenceControllerTest.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.VersionedPackage;
-import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.service.notification.NotificationListenerFilter;
 import android.util.ArraySet;
@@ -65,6 +64,7 @@
     PreferenceScreen mScreen;
     @Mock
     ApplicationsState mAppState;
+
     private ApplicationsState.AppEntry mAppEntry;
     private ApplicationsState.AppEntry mAppEntry2;
 
@@ -92,8 +92,6 @@
 
         mAppEntry.info = ai;
         mAppEntry.label = "hi";
-        Drawable icon = mock(Drawable.class);
-        mAppEntry.icon = icon;
 
         mController = new BridgedAppsPreferenceController(mContext, "key");
         mController.setCn(mCn);
@@ -167,7 +165,7 @@
 
         assertThat(actual.isChecked()).isTrue();
         assertThat(actual.getTitle()).isEqualTo("hi");
-        assertThat(actual.getIcon()).isEqualTo(mAppEntry.icon);
+        assertThat(actual.getIcon()).isNotNull();
     }
 
     @Test
diff --git a/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java
new file mode 100644
index 0000000..3e6ac09
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics/BiometricNavigationUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class BiometricNavigationUtilsTest {
+
+    private static final String SETTINGS_CLASS_NAME = "SettingsClassName";
+    private static final String EXTRA_KEY = "EXTRA_KEY";
+
+    @Mock
+    private UserManager mUserManager;
+    private Context mContext;
+    private BiometricNavigationUtils mBiometricNavigationUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+        doNothing().when(mContext).startActivity(any());
+        mBiometricNavigationUtils = new BiometricNavigationUtils();
+    }
+
+    @Test
+    public void openBiometricSettings_quietMode_launchesQuiteModeDialog() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
+
+        mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME,
+                Bundle.EMPTY);
+
+        assertQuietModeDialogLaunchRequested();
+    }
+
+    @Test
+    public void openBiometricSettings_quietMode_returnsFalse() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
+
+        assertThat(mBiometricNavigationUtils.launchBiometricSettings(
+                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY)).isFalse();
+    }
+
+    @Test
+    public void openBiometricSettings_noQuietMode_emptyExtras_launchesFragmentWithoutExtras() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        mBiometricNavigationUtils.launchBiometricSettings(
+                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY);
+
+        assertSettingsPageLaunchRequested(false /* shouldContainExtras */);
+    }
+
+    @Test
+    public void openBiometricSettings_noQuietMode_emptyExtras_returnsTrue() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        assertThat(mBiometricNavigationUtils.launchBiometricSettings(
+                mContext, SETTINGS_CLASS_NAME, Bundle.EMPTY)).isTrue();
+    }
+
+    @Test
+    public void openBiometricSettings_noQuietMode_withExtras_launchesFragmentWithExtras() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        final Bundle extras = createNotEmptyExtras();
+        mBiometricNavigationUtils.launchBiometricSettings(mContext, SETTINGS_CLASS_NAME, extras);
+
+        assertSettingsPageLaunchRequested(true /* shouldContainExtras */);
+    }
+
+    @Test
+    public void openBiometricSettings_noQuietMode_withExtras_returnsTrue() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        assertThat(mBiometricNavigationUtils.launchBiometricSettings(
+                mContext, SETTINGS_CLASS_NAME, createNotEmptyExtras())).isTrue();
+    }
+
+    private Bundle createNotEmptyExtras() {
+        final Bundle bundle = new Bundle();
+        bundle.putInt(EXTRA_KEY, 0);
+        return bundle;
+    }
+
+    private void assertQuietModeDialogLaunchRequested() {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(intentCaptor.capture());
+
+        Intent intent = intentCaptor.getValue();
+        assertThat(intent.getComponent().getPackageName())
+                .isEqualTo("android");
+        assertThat(intent.getComponent().getClassName())
+                .isEqualTo("com.android.internal.app.UnlaunchableAppActivity");
+    }
+
+    private void assertSettingsPageLaunchRequested(boolean shouldContainExtras) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(intentCaptor.capture());
+
+        Intent intent = intentCaptor.getValue();
+        assertThat(intent.getComponent().getPackageName())
+                .isEqualTo("com.android.settings");
+        assertThat(intent.getComponent().getClassName())
+                .isEqualTo(SETTINGS_CLASS_NAME);
+        assertThat(intent.getExtras().containsKey(EXTRA_KEY)).isEqualTo(shouldContainExtras);
+    }
+
+}
diff --git a/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java
new file mode 100644
index 0000000..55b3fae
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtilsTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.combination;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.Settings;
+import com.android.settings.testutils.ResourcesUtils;
+import com.android.settingslib.RestrictedLockUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CombinedBiometricStatusUtilsTest {
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("package", "class");
+    private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId());
+
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
+
+    private Context mApplicationContext;
+    private CombinedBiometricStatusUtils mCombinedBiometricStatusUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = spy(ApplicationProvider.getApplicationContext());
+        when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE))
+                .thenReturn(COMPONENT_NAME);
+        when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE))
+                .thenReturn(mFingerprintManager);
+        when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(mDevicePolicyManager);
+        when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+        mCombinedBiometricStatusUtils = new CombinedBiometricStatusUtils(mApplicationContext);
+    }
+
+    @Test
+    public void isAvailable_withoutFingerprint_withoutFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        assertThat(mCombinedBiometricStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_withoutFingerprint_whenFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        assertThat(mCombinedBiometricStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_whenFingerprint_withoutFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        assertThat(mCombinedBiometricStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_whenFingerprint_whenFace_returnsTrue() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        assertThat(mCombinedBiometricStatusUtils.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void getDisabledAdmin_whenFingerprintDisabled_whenFaceDisabled_returnsEnforcedAdmin() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME))
+                .thenReturn(KEYGUARD_DISABLE_FACE | KEYGUARD_DISABLE_FINGERPRINT);
+
+        final RestrictedLockUtils.EnforcedAdmin admin =
+                mCombinedBiometricStatusUtils.getDisablingAdmin();
+
+        assertThat(admin).isEqualTo(new RestrictedLockUtils.EnforcedAdmin(
+                COMPONENT_NAME, UserManager.DISALLOW_BIOMETRIC, USER_HANDLE));
+    }
+
+    @Test
+    public void getDisabledAdmin_whenFingerprintDisabled_whenFaceEnabled_returnsNull() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME))
+                .thenReturn(KEYGUARD_DISABLE_FINGERPRINT);
+
+        assertThat(mCombinedBiometricStatusUtils.getDisablingAdmin()).isNull();
+    }
+
+    @Test
+    public void getDisabledAdmin_whenFingerprintEnabled_whenFaceDisabled_returnsNull() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME))
+                .thenReturn(KEYGUARD_DISABLE_FACE);
+
+        assertThat(mCombinedBiometricStatusUtils.getDisablingAdmin()).isNull();
+    }
+
+    @Test
+    public void getDisabledAdmin_whenFingerprintEnabled_whenFaceEnabled_returnsNull() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0);
+
+        assertThat(mCombinedBiometricStatusUtils.getDisablingAdmin()).isNull();
+    }
+
+    @Test
+    public void getSummary_whenFaceEnrolled_whenMultipleFingerprints_returnsBothFpMultiple() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt()))
+                .thenReturn(createFingerprintList(2));
+
+        assertThat(mCombinedBiometricStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_biometric_preference_summary_both_fp_multiple"));
+    }
+
+    @Test
+    public void getSummary_whenFaceEnrolled_whenSingleFingerprint_returnsBothFpSingle() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt()))
+                .thenReturn(createFingerprintList(1));
+
+        assertThat(mCombinedBiometricStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_biometric_preference_summary_both_fp_single"));
+    }
+
+    @Test
+    public void getSummary_whenFaceEnrolled_whenNoFingerprints_returnsFace() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt()))
+                .thenReturn(createFingerprintList(0));
+
+        assertThat(mCombinedBiometricStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_face_preference_summary"));
+    }
+
+    @Test
+    public void getSummary_whenNoFaceEnrolled_whenMultipleFingerprints_returnsFingerprints() {
+        final int enrolledFingerprintsCount = 2;
+        final int stringResId = ResourcesUtils.getResourcesId(
+                ApplicationProvider.getApplicationContext(), "plurals",
+                "security_settings_fingerprint_preference_summary");
+        final String summary = mApplicationContext.getResources().getQuantityString(
+                stringResId, enrolledFingerprintsCount /* quantity */,
+                enrolledFingerprintsCount /* formatArgs */);
+
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt()))
+                .thenReturn(createFingerprintList(enrolledFingerprintsCount));
+
+        assertThat(mCombinedBiometricStatusUtils.getSummary()).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_whenNoFaceEnrolled_whenSingleFingerprints_returnsFingerprints() {
+        final int enrolledFingerprintsCount = 1;
+        final int stringResId = ResourcesUtils.getResourcesId(
+                ApplicationProvider.getApplicationContext(), "plurals",
+                "security_settings_fingerprint_preference_summary");
+        final String summary = mApplicationContext.getResources().getQuantityString(
+                stringResId, enrolledFingerprintsCount /* quantity */,
+                enrolledFingerprintsCount /* formatArgs */);
+
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt()))
+                .thenReturn(createFingerprintList(enrolledFingerprintsCount));
+
+        assertThat(mCombinedBiometricStatusUtils.getSummary()).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSummary_whenNoFaceEnrolled_whenNoFingerprints_returnsNoneEnrolled() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt()))
+                .thenReturn(createFingerprintList(0));
+
+        assertThat(mCombinedBiometricStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_biometric_preference_summary_none_enrolled"));
+    }
+
+    @Test
+    public void getSettingsClassName_returnsCombinedBiometricSettings() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+        assertThat(mCombinedBiometricStatusUtils.getSettingsClassName())
+                .isEqualTo(Settings.CombinedBiometricSettingsActivity.class.getName());
+    }
+
+    @Test
+    public void getProfileSettingsClassName_returnsCombinedBiometricProfileSettings() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+        assertThat(mCombinedBiometricStatusUtils.getProfileSettingsClassName())
+                .isEqualTo(Settings.CombinedBiometricProfileSettingsActivity.class.getName());
+    }
+
+    private List<Fingerprint> createFingerprintList(int size) {
+        final List<Fingerprint> fingerprintList = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            fingerprintList.add(new Fingerprint("fingerprint" + i, 0, 0));
+        }
+        return fingerprintList;
+    }
+}
diff --git a/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java
new file mode 100644
index 0000000..f670fad
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics/face/FaceStatusUtilsTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.Settings;
+import com.android.settings.testutils.ResourcesUtils;
+import com.android.settingslib.RestrictedLockUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class FaceStatusUtilsTest {
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("package", "class");
+    private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId());
+
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
+
+    private Context mApplicationContext;
+    private FaceStatusUtils mFaceStatusUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = spy(ApplicationProvider.getApplicationContext());
+        when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE))
+                .thenReturn(COMPONENT_NAME);
+        when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE))
+                .thenReturn(mFingerprintManager);
+        when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(mDevicePolicyManager);
+        when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+        mFaceStatusUtils = new FaceStatusUtils(mApplicationContext, mFaceManager);
+    }
+
+    @Test
+    public void isAvailable_withoutFingerprint_withoutFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        assertThat(mFaceStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_withoutFingerprint_withFace_returnsTrue() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        assertThat(mFaceStatusUtils.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_withFingerprint_withoutFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        assertThat(mFaceStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_withFingerprint_withFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        assertThat(mFaceStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void getDisabledAdmin_whenFaceDisabled_returnsEnforcedAdmin() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+
+        final RestrictedLockUtils.EnforcedAdmin admin = mFaceStatusUtils.getDisablingAdmin();
+
+        assertThat(admin).isEqualTo(new RestrictedLockUtils.EnforcedAdmin(
+                COMPONENT_NAME, UserManager.DISALLOW_BIOMETRIC, USER_HANDLE));
+    }
+
+    @Test
+    public void getDisabledAdmin_withFaceEnabled_returnsNull() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0);
+
+        assertThat(mFaceStatusUtils.getDisablingAdmin()).isNull();
+    }
+
+    @Test
+    public void getSummary_whenNotEnrolled_returnsSummaryNone() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+        assertThat(mFaceStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_face_preference_summary_none"));
+    }
+
+    @Test
+    public void getSummary_whenEnrolled_returnsSummary() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+        assertThat(mFaceStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_face_preference_summary"));
+    }
+
+    @Test
+    public void getSettingsClassName_whenNotEnrolled_returnsFaceEnrollInduction() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+        assertThat(mFaceStatusUtils.getSettingsClassName())
+                .isEqualTo(FaceEnrollIntroduction.class.getName());
+    }
+
+    @Test
+    public void getSettingsClassName_whenEnrolled_returnsFaceSettings() {
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+        assertThat(mFaceStatusUtils.getSettingsClassName())
+                .isEqualTo(Settings.FaceSettingsActivity.class.getName());
+    }
+}
diff --git a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java
new file mode 100644
index 0000000..b4abb5d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.testutils.ResourcesUtils;
+import com.android.settingslib.RestrictedLockUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class FingerprintStatusUtilsTest {
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("package", "class");
+    private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId());
+
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
+
+    private Context mApplicationContext;
+    private FingerprintStatusUtils mFingerprintStatusUtils;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mApplicationContext = spy(ApplicationProvider.getApplicationContext());
+        when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE))
+                .thenReturn(COMPONENT_NAME);
+        when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE))
+                .thenReturn(mFingerprintManager);
+        when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(mDevicePolicyManager);
+        when(mApplicationContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+        mFingerprintStatusUtils =
+                new FingerprintStatusUtils(mApplicationContext, mFingerprintManager);
+    }
+
+    @Test
+    public void isAvailable_withoutFingerprint_withoutFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        assertThat(mFingerprintStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_withoutFingerprint_withFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        assertThat(mFingerprintStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_withFingerprint_withoutFace_returnsTrue() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+
+        assertThat(mFingerprintStatusUtils.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void isAvailable_withFingerprint_withFace_returnsFalse() {
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+        assertThat(mFingerprintStatusUtils.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void getDisabledAdmin_whenFingerprintDisabled_returnsEnforcedAdmin() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+
+        final RestrictedLockUtils.EnforcedAdmin admin =
+                mFingerprintStatusUtils.getDisablingAdmin();
+
+        assertThat(admin).isEqualTo(new RestrictedLockUtils.EnforcedAdmin(
+                COMPONENT_NAME, UserManager.DISALLOW_BIOMETRIC, USER_HANDLE));
+    }
+
+    @Test
+    public void getDisabledAdmin_withFingerprintEnabled_returnsNull() {
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0);
+
+        assertThat(mFingerprintStatusUtils.getDisablingAdmin()).isNull();
+    }
+
+    @Test
+    public void getSummary_whenNotEnrolled_returnsSummaryNone() {
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+        assertThat(mFingerprintStatusUtils.getSummary())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "security_settings_fingerprint_preference_summary_none"));
+    }
+
+    @Test
+    public void getSummary_whenEnrolled_returnsSummary() {
+        final int enrolledFingerprintsCount = 2;
+        final int stringResId = ResourcesUtils.getResourcesId(
+                ApplicationProvider.getApplicationContext(), "plurals",
+                "security_settings_fingerprint_preference_summary");
+        final String summary = mApplicationContext.getResources().getQuantityString(
+                stringResId, enrolledFingerprintsCount /* quantity */,
+                enrolledFingerprintsCount /* formatArgs */);
+
+        when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true);
+        when(mFingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(
+                createFingerprintList(enrolledFingerprintsCount));
+
+        assertThat(mFingerprintStatusUtils.getSummary()).isEqualTo(summary);
+    }
+
+    @Test
+    public void getSettingsClassName_whenNotEnrolled_returnsFingerprintEnrollInduction() {
+        when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+
+        assertThat(mFingerprintStatusUtils.getSettingsClassName())
+                .isEqualTo(FingerprintEnrollIntroduction.class.getName());
+    }
+
+    @Test
+    public void getSettingsClassName_whenEnrolled_returnsFingerprintSettings() {
+        when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true);
+
+        assertThat(mFingerprintStatusUtils.getSettingsClassName())
+                .isEqualTo(FingerprintSettings.class.getName());
+    }
+
+    private List<Fingerprint> createFingerprintList(int size) {
+        final List<Fingerprint> fingerprintList = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            fingerprintList.add(new Fingerprint("fingerprint" + i, 0, 0));
+        }
+        return fingerprintList;
+    }
+}
diff --git a/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java b/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java
index d4df97f..1cfee0f 100644
--- a/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java
+++ b/tests/unit/src/com/android/settings/privacy/PrivacyDashboardActivityTest.java
@@ -33,7 +33,7 @@
 
 import com.android.settings.Settings;
 import com.android.settings.SettingsActivity;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,14 +48,14 @@
     private static final String DEFAULT_FRAGMENT_CLASSNAME = "DefaultFragmentClassname";
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
     private Settings.PrivacyDashboardActivity mActivity;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
         final Intent intent = new Intent();
         intent.setAction(android.provider.Settings.ACTION_PRIVACY_SETTINGS);
         intent.setClass(InstrumentationRegistry.getInstrumentation().getTargetContext(),
@@ -78,7 +78,7 @@
 
     @Test
     public void onCreate_whenSafetyCenterEnabled_redirectsToSafetyCenter() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true);
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
 
         mActivity.handleSafetyCenterRedirection();
@@ -89,7 +89,7 @@
 
     @Test
     public void onCreate_whenSafetyCenterDisabled_doesntRedirectToSafetyCenter() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
         mActivity.handleSafetyCenterRedirection();
 
         verify(mActivity, times(0)).startActivity(any());
diff --git a/tests/unit/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceControllerTest.java b/tests/unit/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceControllerTest.java
index 495700a..bc6be2e 100644
--- a/tests/unit/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/privacy/TopLevelPrivacyEntryPreferenceControllerTest.java
@@ -26,7 +26,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.security.TopLevelSecurityEntryPreferenceController;
 
 import org.junit.Before;
@@ -43,12 +43,12 @@
     private TopLevelPrivacyEntryPreferenceController mTopLevelPrivacyEntryPreferenceController;
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
 
         mTopLevelPrivacyEntryPreferenceController =
                 new TopLevelPrivacyEntryPreferenceController(
@@ -57,7 +57,7 @@
 
     @Test
     public void getAvailabilityStatus_whenSafetyCenterEnabled_returnsUnavailable() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true);
 
         assertThat(mTopLevelPrivacyEntryPreferenceController.getAvailabilityStatus())
                 .isEqualTo(TopLevelSecurityEntryPreferenceController.CONDITIONALLY_UNAVAILABLE);
@@ -65,7 +65,7 @@
 
     @Test
     public void getAvailabilityStatus_whenSafetyCenterDisabled_returnsAvailable() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
 
         assertThat(mTopLevelPrivacyEntryPreferenceController.getAvailabilityStatus())
                 .isEqualTo(TopLevelSecurityEntryPreferenceController.AVAILABLE);
diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java
index f53d336..2627d24 100644
--- a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java
@@ -41,26 +41,21 @@
     @Mock
     private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
-    @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mApplicationContext = ApplicationProvider.getApplicationContext();
         SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
     }
 
     @After
     public void tearDown() {
         SafetyCenterManagerWrapper.sInstance = null;
-        SafetyCenterStatusHolder.sInstance = null;
     }
 
     @Test
     public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
 
         BiometricsSafetySource.sendSafetyData(mApplicationContext);
 
@@ -70,7 +65,7 @@
     @Test
     // TODO(b/215517420): Adapt this test when method is implemented.
     public void sendSafetyData_whenSafetyCenterIsEnabled_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
 
         BiometricsSafetySource.sendSafetyData(mApplicationContext);
 
diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
index 052f981..64b9692 100644
--- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
@@ -19,19 +19,22 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
 import android.safetycenter.SafetySourceData;
 import android.safetycenter.SafetySourceStatus;
+import android.safetycenter.SafetySourceStatus.IconAction;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.settings.SettingsActivity;
-import com.android.settings.password.ChooseLockGeneric;
+import com.android.settings.security.ScreenLockPreferenceDetailsUtils;
+import com.android.settings.testutils.ResourcesUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -44,56 +47,188 @@
 @RunWith(AndroidJUnit4.class)
 public class LockScreenSafetySourceTest {
 
+    private static final String SUMMARY = "summary";
+    private static final String FAKE_ACTION_CHOOSE_LOCK_GENERIC_FRAGMENT = "choose_lock_generic";
+    private static final String FAKE_ACTION_SCREEN_LOCK_SETTINGS = "screen_lock_settings";
+
     private Context mApplicationContext;
 
     @Mock
     private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private ScreenLockPreferenceDetailsUtils mScreenLockPreferenceDetailsUtils;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mApplicationContext = ApplicationProvider.getApplicationContext();
         SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
     }
 
     @After
     public void tearDown() {
         SafetyCenterManagerWrapper.sInstance = null;
-        SafetyCenterStatusHolder.sInstance = null;
     }
 
     @Test
     public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
 
-        LockScreenSafetySource.sendSafetyData(mApplicationContext);
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
 
         verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
     }
 
     @Test
-    public void sendSafetyData_whenSafetyCenterIsEnabled_sendsPlaceholderData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+    public void sendSafetyData_whenScreenLockIsDisabled_sendsNoData() {
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mScreenLockPreferenceDetailsUtils.isAvailable()).thenReturn(false);
 
-        LockScreenSafetySource.sendSafetyData(mApplicationContext);
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        verify(mSafetyCenterManagerWrapper, never()).sendSafetyCenterUpdate(any(), any());
+    }
+
+    @Test
+    public void sendSafetyData_whenScreenLockIsEnabled_sendsData() {
+        whenScreenLockIsEnabled();
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
         ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
         verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
         SafetySourceData safetySourceData = captor.getValue();
         SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
 
         assertThat(safetySourceData.getId()).isEqualTo(LockScreenSafetySource.SAFETY_SOURCE_ID);
-        assertThat(safetySourceStatus.getTitle().toString()).isEqualTo("Lock Screen");
+        assertThat(safetySourceStatus.getTitle().toString())
+                .isEqualTo(ResourcesUtils.getResourcesString(
+                        mApplicationContext,
+                        "unlock_set_unlock_launch_picker_title"));
         assertThat(safetySourceStatus.getSummary().toString())
-                .isEqualTo("Lock screen settings");
+                .isEqualTo(SUMMARY);
+        assertThat(safetySourceStatus.getPendingIntent().getIntent()).isNotNull();
+        assertThat(safetySourceStatus.getPendingIntent().getIntent().getAction())
+                .isEqualTo(FAKE_ACTION_CHOOSE_LOCK_GENERIC_FRAGMENT);
+    }
+
+    @Test
+    public void sendSafetyData_whenLockPatternIsSecure_sendsStatusLevelOk() {
+        whenScreenLockIsEnabled();
+        when(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).thenReturn(true);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
         assertThat(safetySourceStatus.getStatusLevel())
                 .isEqualTo(SafetySourceStatus.STATUS_LEVEL_OK);
-        assertThat(safetySourceStatus.getPendingIntent()).isNotNull();
-        assertThat(safetySourceStatus.getPendingIntent().getIntent().getStringExtra(
-                SettingsActivity.EXTRA_SHOW_FRAGMENT))
-                .isEqualTo(ChooseLockGeneric.ChooseLockGenericFragment.class.getName());
+    }
+
+    @Test
+    public void sendSafetyData_whenLockPatternIsNotSecure_sendsStatusLevelRecommendation() {
+        whenScreenLockIsEnabled();
+        when(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).thenReturn(false);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceStatus.getStatusLevel())
+                .isEqualTo(SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION);
+    }
+
+    @Test
+    public void sendSafetyData_whenPasswordQualityIsManaged_sendsDisabled() {
+        whenScreenLockIsEnabled();
+        when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
+                .thenReturn(true);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceStatus.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void sendSafetyData_whenPasswordQualityIsNotManaged_sendsEnabled() {
+        whenScreenLockIsEnabled();
+        when(mScreenLockPreferenceDetailsUtils.isPasswordQualityManaged(anyInt(), any()))
+                .thenReturn(false);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceStatus.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void sendSafetyData_whenShouldShowGearMenu_sendsGearMenuActionIcon() {
+        whenScreenLockIsEnabled();
+        final Intent launchScreenLockSettings = new Intent(FAKE_ACTION_SCREEN_LOCK_SETTINGS);
+        when(mScreenLockPreferenceDetailsUtils.getLaunchScreenLockSettingsIntent())
+                .thenReturn(launchScreenLockSettings);
+        when(mScreenLockPreferenceDetailsUtils.shouldShowGearMenu()).thenReturn(true);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        final ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(
+                SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        final IconAction iconAction = captor.getValue().getStatus().getIconAction();
+
+        assertThat(iconAction.getIconType()).isEqualTo(IconAction.ICON_TYPE_GEAR);
+        assertThat(iconAction.getPendingIntent().getIntent().getAction())
+                .isEqualTo(FAKE_ACTION_SCREEN_LOCK_SETTINGS);
+    }
+
+    @Test
+    public void sendSafetyData_whenShouldNotShowGearMenu_sendsNoGearMenuActionIcon() {
+        whenScreenLockIsEnabled();
+        when(mScreenLockPreferenceDetailsUtils.shouldShowGearMenu()).thenReturn(false);
+
+        LockScreenSafetySource.sendSafetyData(mApplicationContext,
+                mScreenLockPreferenceDetailsUtils);
+
+        ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+        verify(mSafetyCenterManagerWrapper).sendSafetyCenterUpdate(any(), captor.capture());
+        SafetySourceData safetySourceData = captor.getValue();
+        SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+        assertThat(safetySourceStatus.getIconAction()).isNull();
+    }
+
+    private void whenScreenLockIsEnabled() {
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mScreenLockPreferenceDetailsUtils.isAvailable()).thenReturn(true);
+        when(mScreenLockPreferenceDetailsUtils.getSummary(anyInt())).thenReturn(SUMMARY);
+
+        Intent launchChooseLockGenericFragment = new Intent(
+                FAKE_ACTION_CHOOSE_LOCK_GENERIC_FRAGMENT);
+        when(mScreenLockPreferenceDetailsUtils.getLaunchChooseLockGenericFragmentIntent())
+                .thenReturn(launchChooseLockGenericFragment);
     }
 }
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetyCenterStatusHolderTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetyCenterManagerWrapperTest.java
similarity index 87%
rename from tests/unit/src/com/android/settings/safetycenter/SafetyCenterStatusHolderTest.java
rename to tests/unit/src/com/android/settings/safetycenter/SafetyCenterManagerWrapperTest.java
index 2a31eb8..432a5d6 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetyCenterStatusHolderTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetyCenterManagerWrapperTest.java
@@ -32,7 +32,7 @@
 import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
-public class SafetyCenterStatusHolderTest {
+public class SafetyCenterManagerWrapperTest {
 
     @Mock
     private Context mContext;
@@ -44,13 +44,13 @@
 
     @Test
     public void isEnabled_whenContextNull_returnsFalse() {
-        assertThat(SafetyCenterStatusHolder.get().isEnabled(null)).isFalse();
+        assertThat(SafetyCenterManagerWrapper.get().isEnabled(null)).isFalse();
     }
 
     @Test
     public void isEnabled_whenSystemServiceNull_returnsFalse() {
         when(mContext.getSystemService(SafetyCenterManager.class)).thenReturn(null);
 
-        assertThat(SafetyCenterStatusHolder.get().isEnabled(mContext)).isFalse();
+        assertThat(SafetyCenterManagerWrapper.get().isEnabled(mContext)).isFalse();
     }
 }
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
index 6c9addd..f2a28ff 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -34,6 +34,9 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -51,25 +54,26 @@
     private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private LockPatternUtils mLockPatternUtils;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mApplicationContext = ApplicationProvider.getApplicationContext();
+        final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+        when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext))
+                .thenReturn(mLockPatternUtils);
         SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
     }
 
     @After
     public void tearDown() {
         SafetyCenterManagerWrapper.sInstance = null;
-        SafetyCenterStatusHolder.sInstance = null;
     }
 
     @Test
     public void sendSafetyData_whenSafetyCenterIsEnabled_withNoIntentAction_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent = new Intent().putExtra(EXTRA_REFRESH_SAFETY_SOURCE_IDS, new String[]{});
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
@@ -79,7 +83,7 @@
 
     @Test
     public void sendSafetyData_whenSafetyCenterIsDisabled_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
         Intent intent =
                 new Intent()
                         .setAction(ACTION_REFRESH_SAFETY_SOURCES)
@@ -94,7 +98,7 @@
 
     @Test
     public void sendSafetyData_whenSafetyCenterIsEnabled_withNullSourceIds_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent = new Intent().setAction(ACTION_REFRESH_SAFETY_SOURCES);
 
         new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
@@ -104,7 +108,7 @@
 
     @Test
     public void sendSafetyData_whenSafetyCenterIsEnabled_withNoSourceIds_sendsNoData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent =
                 new Intent()
                         .setAction(ACTION_REFRESH_SAFETY_SOURCES)
@@ -117,7 +121,7 @@
 
     @Test
     public void sendSafetyData_withLockscreenSourceId_sendsLockscreenData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent =
                 new Intent()
                         .setAction(ACTION_REFRESH_SAFETY_SOURCES)
@@ -136,7 +140,7 @@
 
     @Test
     public void sendSafetyData_withBiometricsSourceId_sendsBiometricData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent =
                 new Intent()
                         .setAction(ACTION_REFRESH_SAFETY_SOURCES)
@@ -152,7 +156,7 @@
 
     @Test
     public void sendSafetyData_onBootCompleted_sendsBiometricAndLockscreenData() {
-        when(mSafetyCenterStatusHolder.isEnabled(mApplicationContext)).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
         Intent intent = new Intent().setAction(Intent.ACTION_BOOT_COMPLETED);
 
         // TODO(b/215517420): Update this test when BiometricSafetySource is implemented to test
diff --git a/tests/unit/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceControllerTest.java b/tests/unit/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceControllerTest.java
index e9c451e..25cd774 100644
--- a/tests/unit/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/TopLevelSafetyCenterEntryPreferenceControllerTest.java
@@ -32,6 +32,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,7 +50,7 @@
     private Preference mPreference;
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Mock
     private Context mContext;
@@ -57,7 +58,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
 
         mPreference = new Preference(ApplicationProvider.getApplicationContext());
         mPreference.setKey(PREFERENCE_KEY);
@@ -67,6 +68,11 @@
                 new TopLevelSafetyCenterEntryPreferenceController(mContext, PREFERENCE_KEY);
     }
 
+    @After
+    public void tearDown() {
+        SafetyCenterManagerWrapper.sInstance = null;
+    }
+
     @Test
     public void handlePreferenceTreeClick_forDifferentPreferenceKey_isNotHandled() {
         Preference preference = new Preference(ApplicationProvider.getApplicationContext());
@@ -104,7 +110,7 @@
 
     @Test
     public void getAvailabilityStatus_whenSafetyCenterDisabled_returnsUnavailable() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
 
         assertThat(mTopLevelSafetyCenterEntryPreferenceController.getAvailabilityStatus())
                 .isEqualTo(TopLevelSafetyCenterEntryPreferenceController.CONDITIONALLY_UNAVAILABLE);
@@ -112,7 +118,7 @@
 
     @Test
     public void getAvailabilityStatus_whenSafetyCenterEnabled_returnsAvailable() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true);
 
         assertThat(mTopLevelSafetyCenterEntryPreferenceController.getAvailabilityStatus())
                 .isEqualTo(TopLevelSafetyCenterEntryPreferenceController.AVAILABLE);
diff --git a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
index 3d13b48..f471128 100644
--- a/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
+++ b/tests/unit/src/com/android/settings/security/ScreenLockPreferenceDetailsUtilsTest.java
@@ -85,7 +85,7 @@
                 .thenReturn(mDevicePolicyManager);
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         doNothing().when(mContext).startActivity(any());
-        when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[] {});
+        when(mUserManager.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[]{});
 
         final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
         when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext))
@@ -254,6 +254,20 @@
     }
 
     @Test
+    public void isLockPatternSecure_patternIsSecure_shouldReturnTrue() {
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).isTrue();
+    }
+
+    @Test
+    public void isLockPatternSecure_patternIsNotSecure_shouldReturnFalse() {
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false);
+
+        assertThat(mScreenLockPreferenceDetailsUtils.isLockPatternSecure()).isFalse();
+    }
+
+    @Test
     public void shouldShowGearMenu_patternIsSecure_shouldReturnTrue() {
         when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
 
@@ -275,6 +289,13 @@
     }
 
     @Test
+    public void getLaunchScreenLockSettingsIntent_returnsIntent() {
+        final Intent intent = mScreenLockPreferenceDetailsUtils.getLaunchScreenLockSettingsIntent();
+
+        assertFragmentLaunchIntent(intent, ScreenLockSettings.class.getName());
+    }
+
+    @Test
     public void openChooseLockGenericFragment_noQuietMode_shouldSendIntent_shouldReturnTrue() {
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
 
@@ -282,6 +303,17 @@
         assertFragmentLaunchRequested(ChooseLockGeneric.ChooseLockGenericFragment.class.getName());
     }
 
+    @Test
+    public void getLaunchChooseLockGenericFragmentIntent_noQuietMode_returnsIntent() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+
+        final Intent intent =
+                mScreenLockPreferenceDetailsUtils.getLaunchChooseLockGenericFragmentIntent();
+
+        assertFragmentLaunchIntent(intent,
+                ChooseLockGeneric.ChooseLockGenericFragment.class.getName());
+    }
+
     private void whenConfigShowUnlockSetOrChangeIsEnabled(boolean enabled) {
         final int resId = ResourcesUtils.getResourcesId(
                 ApplicationProvider.getApplicationContext(), "bool",
@@ -301,6 +333,10 @@
         verify(mContext).startActivity(intentCaptor.capture());
 
         Intent intent = intentCaptor.getValue();
+        assertFragmentLaunchIntent(intent, fragmentClassName);
+    }
+
+    private void assertFragmentLaunchIntent(Intent intent, String fragmentClassName) {
         assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                 .isEqualTo(fragmentClassName);
         assertThat(intent.getIntExtra(
diff --git a/tests/unit/src/com/android/settings/security/SecurityAdvancedSettingsTest.java b/tests/unit/src/com/android/settings/security/SecurityAdvancedSettingsTest.java
index ac91abb..2efb357 100644
--- a/tests/unit/src/com/android/settings/security/SecurityAdvancedSettingsTest.java
+++ b/tests/unit/src/com/android/settings/security/SecurityAdvancedSettingsTest.java
@@ -29,7 +29,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.ResourcesUtils;
 import com.android.settingslib.drawer.CategoryKey;
@@ -51,7 +51,7 @@
     private SecurityAdvancedSettings mSecurityAdvancedSettings;
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Before
     @UiThreadTest
@@ -61,7 +61,7 @@
             Looper.prepare();
         }
 
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
         mContext = ApplicationProvider.getApplicationContext();
 
         mSecurityAdvancedSettings = spy(new SecurityAdvancedSettings());
@@ -76,7 +76,7 @@
 
     @Test
     public void getCategoryKey_whenSafetyCenterIsEnabled_returnsSecurity() {
-        when(mSafetyCenterStatusHolder.isEnabled(any())).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(any())).thenReturn(true);
 
         assertThat(mSecurityAdvancedSettings.getCategoryKey())
                 .isEqualTo(CategoryKey.CATEGORY_SECURITY_ADVANCED_SETTINGS);
@@ -84,7 +84,7 @@
 
     @Test
     public void getCategoryKey_whenAlternativeFragmentPresented_returnsAlternative() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
         setupAlternativeFragment(true, ALTERNATIVE_CATEGORY_KEY);
 
         assertThat(mSecurityAdvancedSettings.getCategoryKey())
@@ -93,7 +93,7 @@
 
     @Test
     public void getCategoryKey_whenNoAlternativeFragmentPresented_returnsLegacy() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
         setupAlternativeFragment(false, null);
 
         assertThat(mSecurityAdvancedSettings.getCategoryKey())
diff --git a/tests/unit/src/com/android/settings/security/SecurityDashboardActivityTest.java b/tests/unit/src/com/android/settings/security/SecurityDashboardActivityTest.java
index 3348663..82112c9 100644
--- a/tests/unit/src/com/android/settings/security/SecurityDashboardActivityTest.java
+++ b/tests/unit/src/com/android/settings/security/SecurityDashboardActivityTest.java
@@ -33,7 +33,7 @@
 
 import com.android.settings.Settings;
 import com.android.settings.SettingsActivity;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -53,14 +53,14 @@
     private Intent mDefaultIntent;
 
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         FakeFeatureFactory mFeatureFactory = FakeFeatureFactory.setupForTest();
         mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider();
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
 
         mDefaultIntent = new Intent();
         mDefaultIntent.setAction(android.provider.Settings.ACTION_SECURITY_SETTINGS);
@@ -122,7 +122,7 @@
 
     @Test
     public void onCreate_whenSafetyCenterEnabled_redirectsToSafetyCenter() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true);
         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
 
         mActivity.handleSafetyCenterRedirection();
@@ -133,7 +133,7 @@
 
     @Test
     public void onCreate_whenSafetyCenterDisabled_doesntRedirectToSafetyCenter() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
 
         mActivity.handleSafetyCenterRedirection();
 
diff --git a/tests/unit/src/com/android/settings/security/TopLevelSecurityEntryPreferenceControllerTest.java b/tests/unit/src/com/android/settings/security/TopLevelSecurityEntryPreferenceControllerTest.java
index 81ebda6..eb8fa17 100644
--- a/tests/unit/src/com/android/settings/security/TopLevelSecurityEntryPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/security/TopLevelSecurityEntryPreferenceControllerTest.java
@@ -31,7 +31,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.settings.SettingsActivity;
-import com.android.settings.safetycenter.SafetyCenterStatusHolder;
+import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -55,14 +55,14 @@
     @Mock
     private Context mContext;
     @Mock
-    private SafetyCenterStatusHolder mSafetyCenterStatusHolder;
+    private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider();
-        SafetyCenterStatusHolder.sInstance = mSafetyCenterStatusHolder;
+        SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
 
         mPreference = new Preference(ApplicationProvider.getApplicationContext());
         mPreference.setKey(PREFERENCE_KEY);
@@ -128,7 +128,7 @@
 
     @Test
     public void getAvailabilityStatus_whenSafetyCenterEnabled_returnsUnavailable() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(true);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(true);
 
         assertThat(mTopLevelSecurityEntryPreferenceController.getAvailabilityStatus())
                 .isEqualTo(TopLevelSecurityEntryPreferenceController.CONDITIONALLY_UNAVAILABLE);
@@ -136,7 +136,7 @@
 
     @Test
     public void getAvailabilityStatus_whenSafetyCenterDisabled_returnsAvailable() {
-        when(mSafetyCenterStatusHolder.isEnabled(any(Context.class))).thenReturn(false);
+        when(mSafetyCenterManagerWrapper.isEnabled(any(Context.class))).thenReturn(false);
 
         assertThat(mTopLevelSecurityEntryPreferenceController.getAvailabilityStatus())
                 .isEqualTo(TopLevelSecurityEntryPreferenceController.AVAILABLE);