[automerger skipped] Merge "Change nullability of inheriting function of PinPrivacyPreferenceController" into main am: 1937711c8a am: e75ae19e0f -s ours am: 19688da90e -s ours am: 9517e0e202 -s ours

am skip reason: Merged-In Idbfa244921957be2bba3cd02e5e873e81e8807d8 with SHA-1 05514d0f4d is already in history

Original change: https://android-review.googlesource.com/c/platform/packages/apps/Settings/+/2717673

Change-Id: Ic749bb0f4e66e8cf0d7828901bf2a71ea12750f4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 4d07913..31895db 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,7 @@
         "androidx.appcompat_appcompat",
         "androidx.cardview_cardview",
         "androidx.compose.runtime_runtime-livedata",
+        "androidx.activity_activity-ktx",
         "androidx.preference_preference",
         "androidx.recyclerview_recyclerview",
         "androidx.window_window",
@@ -82,6 +83,8 @@
         "net-utils-framework-common",
         "app-usage-event-protos-lite",
         "battery-event-protos-lite",
+        "battery-usage-slot-protos-lite",
+        "power-anomaly-event-protos-lite",
         "settings-contextual-card-protos-lite",
         "settings-log-bridge-protos-lite",
         "settings-telephony-protos-lite",
@@ -149,14 +152,17 @@
     srcs: ["proguard.flags"],
 }
 
-// The sources for Settings need to be exposed to SettingsGoogle, etc.
-// so they can run the com.android.settingslib.search.IndexableProcessor
-// over all the sources together.
+// Deprecated. The sources for Settings need to be exposed to ArcSettings, so they can run the
+// com.android.settingslib.search.IndexableProcessor over all the sources together.
+// Use "-Acom.android.settingslib.search.processor.package=" instead to generate the search data
+// separately for different modules.
 filegroup {
     name: "Settings_srcs",
     srcs: ["src/**/*.java", "src/**/*.kt"],
 }
 
+// Deprecated. Do not depend on this, only depend on Settings-core, and its manifest is also
+// included.
 filegroup {
     name: "Settings_manifest",
     srcs: ["AndroidManifest.xml"],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index df4ad39..1387580 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.HARDWARE_TEST" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
     <uses-permission android:name="android.permission.QUERY_AUDIO_STATE" />
     <uses-permission android:name="android.permission.MASTER_CLEAR" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
@@ -1577,6 +1578,19 @@
                        android:value="@string/menu_key_apps"/>
         </activity-alias>
 
+        <activity android:name="Settings$UserAspectRatioAppListActivity"
+            android:exported="true"
+            android:label="@string/aspect_ratio_title">
+            <intent-filter android:priority="1">
+                <action android:name="android.settings.MANAGE_USER_ASPECT_RATIO_SETTINGS"/>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                android:value="com.android.settings.applications.manageapplications.ManageApplications" />
+            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+                android:value="@string/menu_key_apps"/>
+        </activity>
+
         <activity
             android:name="Settings$ManageDomainUrlsActivity"
             android:exported="true"
@@ -2395,6 +2409,8 @@
             <intent-filter android:priority="1">
                 <action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL" />
                 <action android:name="android.app.action.CONFIRM_FRP_CREDENTIAL" />
+                <action android:name="android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL" />
+                <action android:name="android.app.action.CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -4837,7 +4853,7 @@
         </activity>
 
         <activity android:name="Settings$FactoryResetActivity"
-                  android:permission="android.permission.BACKUP"
+                  android:permission="android.permission.MASTER_CLEAR"
                   android:label="@string/main_clear_title"
                   android:exported="true"
                   android:theme="@style/SudThemeGlif.Light">
@@ -4902,6 +4918,20 @@
         <activity android:name=".spa.SpaBridgeActivity" android:exported="false"/>
         <activity android:name=".spa.SpaAppBridgeActivity" android:exported="false"/>
 
+        <activity android:name=".Settings$FingerprintSettingsActivityV2"
+            android:label="@string/security_settings_fingerprint_preference_title"
+            android:exported="false"
+            android:icon="@drawable/ic_fingerprint_header">
+            <intent-filter>
+                <action android:name="android.settings.FINGERPRINT_SETTINGS_V2" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                android:value="com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment" />
+            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+                android:value="@string/menu_key_security"/>
+        </activity>
+
         <activity-alias android:name="UsageStatsActivity"
                         android:exported="true"
                         android:label="@string/testing_usage_stats"
diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto
index 150c2e2..e75ca48 100644
--- a/protos/fuelgauge_log.proto
+++ b/protos/fuelgauge_log.proto
@@ -5,13 +5,12 @@
 option java_package = "com.android.settings.fuelgauge";
 option java_outer_classname = "FuelgaugeLogProto";
 
-// Stores history of setting optimize mode
+// Store history of setting optimize mode
 message BatteryOptimizeHistoricalLog {
   repeated BatteryOptimizeHistoricalLogEntry log_entry = 1;
 }
 
 message BatteryOptimizeHistoricalLogEntry {
-
   // The action to set optimize mode
   enum Action {
     UNKNOWN = 0;
@@ -28,3 +27,25 @@
   optional string action_description = 3;
   optional int64 timestamp = 4;
 }
+
+
+// Store history of battery usage periodic job
+message BatteryUsageHistoricalLog {
+  repeated BatteryUsageHistoricalLogEntry log_entry = 1;
+}
+
+message BatteryUsageHistoricalLogEntry {
+  // The action to record battery usage job event
+  enum Action {
+    UNKNOWN = 0;
+    SCHEDULE_JOB = 1;
+    EXECUTE_JOB = 2;
+    RECHECK_JOB = 3;
+    FETCH_USAGE_DATA = 4;
+    INSERT_USAGE_DATA = 5;
+  }
+
+  optional int64 timestamp = 1;
+  optional Action action = 2;
+  optional string action_description = 3;
+}
diff --git a/res/drawable-night/ic_app_aspect_ratio_16_9.xml b/res/drawable-night/ic_app_aspect_ratio_16_9.xml
new file mode 100644
index 0000000..069003e
--- /dev/null
+++ b/res/drawable-night/ic_app_aspect_ratio_16_9.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.85,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.22C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.22,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#80868B"/>
+    <path
+        android:pathData="M155,53L259,53A4,4 0,0 1,263 57L263,243A4,4 0,0 1,259 247L155,247A4,4 0,0 1,151 243L151,57A4,4 0,0 1,155 53z"
+        android:fillColor="#669DF6"/>
+    <path
+        android:pathData="M157,57L257,57A2,2 0,0 1,259 59L259,241A2,2 0,0 1,257 243L157,243A2,2 0,0 1,155 241L155,59A2,2 0,0 1,157 57z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M171.48,237H161V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M161,237L176,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M242.52,63L253,63L253,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M253,63L238,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable-night/ic_app_aspect_ratio_3_2.xml b/res/drawable-night/ic_app_aspect_ratio_3_2.xml
new file mode 100644
index 0000000..22c0969
--- /dev/null
+++ b/res/drawable-night/ic_app_aspect_ratio_3_2.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.85,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.22C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.22,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#80868B"/>
+    <path
+        android:pathData="M144,53L268,53A4,4 0,0 1,272 57L272,243A4,4 0,0 1,268 247L144,247A4,4 0,0 1,140 243L140,57A4,4 0,0 1,144 53z"
+        android:fillColor="#669DF6"/>
+    <path
+        android:pathData="M146,57L266,57A2,2 0,0 1,268 59L268,241A2,2 0,0 1,266 243L146,243A2,2 0,0 1,144 241L144,59A2,2 0,0 1,146 57z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M160.48,237H150V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M150,237L165,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M251.52,63L262,63L262,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M262,63L247,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable-night/ic_app_aspect_ratio_4_3.xml b/res/drawable-night/ic_app_aspect_ratio_4_3.xml
new file mode 100644
index 0000000..0238311
--- /dev/null
+++ b/res/drawable-night/ic_app_aspect_ratio_4_3.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.85,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.22C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.22,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#80868B"/>
+    <path
+        android:pathData="M136,53L276,53A4,4 0,0 1,280 57L280,243A4,4 0,0 1,276 247L136,247A4,4 0,0 1,132 243L132,57A4,4 0,0 1,136 53z"
+        android:fillColor="#669DF6"/>
+    <path
+        android:pathData="M138,57L274,57A2,2 0,0 1,276 59L276,241A2,2 0,0 1,274 243L138,243A2,2 0,0 1,136 241L136,59A2,2 0,0 1,138 57z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M152.48,237H142V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M142,237L157,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M259.52,63L270,63L270,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M270,63L255,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable-night/ic_app_aspect_ratio_display_size.xml b/res/drawable-night/ic_app_aspect_ratio_display_size.xml
new file mode 100644
index 0000000..91626d7
--- /dev/null
+++ b/res/drawable-night/ic_app_aspect_ratio_display_size.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.85,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.22C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.22,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#80868B"/>
+    <path
+        android:pathData="M128,53L284,53A4,4 0,0 1,288 57L288,243A4,4 0,0 1,284 247L128,247A4,4 0,0 1,124 243L124,57A4,4 0,0 1,128 53z"
+        android:fillColor="#669DF6"/>
+    <path
+        android:pathData="M130,57L282,57A2,2 0,0 1,284 59L284,241A2,2 0,0 1,282 243L130,243A2,2 0,0 1,128 241L128,59A2,2 0,0 1,130 57z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M144.48,237H134V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M134,237L149,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M267.52,63L278,63L278,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M278,63L263,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable-night/ic_app_aspect_ratio_fullscreen.xml b/res/drawable-night/ic_app_aspect_ratio_fullscreen.xml
new file mode 100644
index 0000000..aecc8f0
--- /dev/null
+++ b/res/drawable-night/ic_app_aspect_ratio_fullscreen.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.85,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.22C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.22,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#80868B"/>
+    <path
+        android:pathData="M100,53L312,53A4,4 0,0 1,316 57L316,243A4,4 0,0 1,312 247L100,247A4,4 0,0 1,96 243L96,57A4,4 0,0 1,100 53z"
+        android:fillColor="#669DF6"/>
+    <path
+        android:pathData="M102,57L310,57A2,2 0,0 1,312 59L312,241A2,2 0,0 1,310 243L102,243A2,2 0,0 1,100 241L100,59A2,2 0,0 1,102 57z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M116.48,237H106V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M106,237L121,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M295.52,63L306,63L306,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M306,63L291,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable-night/ic_app_aspect_ratio_half_screen.xml b/res/drawable-night/ic_app_aspect_ratio_half_screen.xml
new file mode 100644
index 0000000..af533ea
--- /dev/null
+++ b/res/drawable-night/ic_app_aspect_ratio_half_screen.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.85,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.22C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.22,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#80868B"/>
+    <path
+        android:pathData="M148,53L264,53A4,4 0,0 1,268 57L268,243A4,4 0,0 1,264 247L148,247A4,4 0,0 1,144 243L144,57A4,4 0,0 1,148 53z"
+        android:fillColor="#669DF6"/>
+    <path
+        android:pathData="M150,57L262,57A2,2 0,0 1,264 59L264,241A2,2 0,0 1,262 243L150,243A2,2 0,0 1,148 241L148,59A2,2 0,0 1,150 57z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M164.48,237H154V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M154,237L169,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M247.52,63L258,63L258,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M258,63L243,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#669DF6"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/action_button_bg.xml b/res/drawable/action_button_bg.xml
new file mode 100644
index 0000000..b50cc41
--- /dev/null
+++ b/res/drawable/action_button_bg.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:color="?android:attr/colorControlHighlight">
+    <item>
+        <inset
+            android:insetLeft="0dp"
+            android:insetTop="8dp"
+            android:insetRight="0dp"
+            android:insetBottom="8dp">
+            <shape android:shape="rectangle">
+                <corners android:radius="8dp" />
+                <stroke android:width="1dp"
+                    android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+            </shape>
+        </inset>
+    </item>
+</ripple>
+
diff --git a/res/drawable/battery_tips_all_rounded_bg.xml b/res/drawable/battery_tips_all_rounded_bg.xml
new file mode 100644
index 0000000..4f61f54
--- /dev/null
+++ b/res/drawable/battery_tips_all_rounded_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/settingslib_dialog_background" />
+    <corners android:radius="@dimen/battery_tips_card_corner_radius_normal" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/battery_tips_all_rounded_bg_ripple.xml b/res/drawable/battery_tips_all_rounded_bg_ripple.xml
new file mode 100644
index 0000000..3180570
--- /dev/null
+++ b/res/drawable/battery_tips_all_rounded_bg_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:drawable="@drawable/battery_tips_all_rounded_bg"/>
+</ripple>
\ No newline at end of file
diff --git a/res/drawable/battery_tips_half_rounded_bottom_bg.xml b/res/drawable/battery_tips_half_rounded_bottom_bg.xml
new file mode 100644
index 0000000..7766de6
--- /dev/null
+++ b/res/drawable/battery_tips_half_rounded_bottom_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/settingslib_dialog_background"/>
+    <corners
+        android:topLeftRadius="@dimen/battery_tips_card_corner_radius_small"
+        android:topRightRadius="@dimen/battery_tips_card_corner_radius_small"
+        android:bottomLeftRadius="@dimen/battery_tips_card_corner_radius_normal"
+        android:bottomRightRadius="@dimen/battery_tips_card_corner_radius_normal"
+        />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/battery_tips_half_rounded_top_bg.xml b/res/drawable/battery_tips_half_rounded_top_bg.xml
new file mode 100644
index 0000000..aba1a4f
--- /dev/null
+++ b/res/drawable/battery_tips_half_rounded_top_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/settingslib_dialog_background"/>
+    <corners
+        android:topLeftRadius="@dimen/battery_tips_card_corner_radius_normal"
+        android:topRightRadius="@dimen/battery_tips_card_corner_radius_normal"
+        android:bottomLeftRadius="@dimen/battery_tips_card_corner_radius_small"
+        android:bottomRightRadius="@dimen/battery_tips_card_corner_radius_small"
+        />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_app_aspect_ratio_16_9.xml b/res/drawable/ic_app_aspect_ratio_16_9.xml
new file mode 100644
index 0000000..2300f3d
--- /dev/null
+++ b/res/drawable/ic_app_aspect_ratio_16_9.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.84,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.21C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.21,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#DADCE0"/>
+    <path
+        android:pathData="M155,53L259,53A4,4 0,0 1,263 57L263,243A4,4 0,0 1,259 247L155,247A4,4 0,0 1,151 243L151,57A4,4 0,0 1,155 53z"
+        android:fillColor="#1A73E8"/>
+    <path
+        android:pathData="M157,57L257,57A2,2 0,0 1,259 59L259,241A2,2 0,0 1,257 243L157,243A2,2 0,0 1,155 241L155,59A2,2 0,0 1,157 57z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M171.48,237H161V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M161,237L176,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M242.52,63L253,63L253,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M253,63L238,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/ic_app_aspect_ratio_3_2.xml b/res/drawable/ic_app_aspect_ratio_3_2.xml
new file mode 100644
index 0000000..b28bdd4
--- /dev/null
+++ b/res/drawable/ic_app_aspect_ratio_3_2.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.84,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.21C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.21,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#DADCE0"/>
+    <path
+        android:pathData="M144,53L268,53A4,4 0,0 1,272 57L272,243A4,4 0,0 1,268 247L144,247A4,4 0,0 1,140 243L140,57A4,4 0,0 1,144 53z"
+        android:fillColor="#1A73E8"/>
+    <path
+        android:pathData="M146,57L266,57A2,2 0,0 1,268 59L268,241A2,2 0,0 1,266 243L146,243A2,2 0,0 1,144 241L144,59A2,2 0,0 1,146 57z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M160.48,237H150V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M150,237L165,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M251.52,63L262,63L262,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M262,63L247,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/ic_app_aspect_ratio_4_3.xml b/res/drawable/ic_app_aspect_ratio_4_3.xml
new file mode 100644
index 0000000..ba875e9
--- /dev/null
+++ b/res/drawable/ic_app_aspect_ratio_4_3.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.84,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.21C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.21,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#DADCE0"/>
+    <path
+        android:pathData="M136,53L276,53A4,4 0,0 1,280 57L280,243A4,4 0,0 1,276 247L136,247A4,4 0,0 1,132 243L132,57A4,4 0,0 1,136 53z"
+        android:fillColor="#1A73E8"/>
+    <path
+        android:pathData="M138,57L274,57A2,2 0,0 1,276 59L276,241A2,2 0,0 1,274 243L138,243A2,2 0,0 1,136 241L136,59A2,2 0,0 1,138 57z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M152.48,237H142V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M142,237L157,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M259.52,63L270,63L270,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M270,63L255,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/ic_app_aspect_ratio_display_size.xml b/res/drawable/ic_app_aspect_ratio_display_size.xml
new file mode 100644
index 0000000..1122395
--- /dev/null
+++ b/res/drawable/ic_app_aspect_ratio_display_size.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.84,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.21C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.21,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#DADCE0"/>
+    <path
+        android:pathData="M128,53L284,53A4,4 0,0 1,288 57L288,243A4,4 0,0 1,284 247L128,247A4,4 0,0 1,124 243L124,57A4,4 0,0 1,128 53z"
+        android:fillColor="#1A73E8"/>
+    <path
+        android:pathData="M130,57L282,57A2,2 0,0 1,284 59L284,241A2,2 0,0 1,282 243L130,243A2,2 0,0 1,128 241L128,59A2,2 0,0 1,130 57z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M144.48,237H134V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M134,237L149,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M267.52,63L278,63L278,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M278,63L263,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/ic_app_aspect_ratio_fullscreen.xml b/res/drawable/ic_app_aspect_ratio_fullscreen.xml
new file mode 100644
index 0000000..0e62fe5
--- /dev/null
+++ b/res/drawable/ic_app_aspect_ratio_fullscreen.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.84,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.21C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.21,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#DADCE0"/>
+    <path
+        android:pathData="M100,53L312,53A4,4 0,0 1,316 57L316,243A4,4 0,0 1,312 247L100,247A4,4 0,0 1,96 243L96,57A4,4 0,0 1,100 53z"
+        android:fillColor="#1A73E8"/>
+    <path
+        android:pathData="M102,57L310,57A2,2 0,0 1,312 59L312,241A2,2 0,0 1,310 243L102,243A2,2 0,0 1,100 241L100,59A2,2 0,0 1,102 57z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M116.48,237H106V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M106,237L121,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M295.52,63L306,63L306,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M306,63L291,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/ic_app_aspect_ratio_half_screen.xml b/res/drawable/ic_app_aspect_ratio_half_screen.xml
new file mode 100644
index 0000000..43afce1
--- /dev/null
+++ b/res/drawable/ic_app_aspect_ratio_half_screen.xml
@@ -0,0 +1,66 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="412dp"
+    android:height="300dp"
+    android:viewportWidth="412"
+    android:viewportHeight="300">
+  <group>
+    <clip-path
+        android:pathData="M0,0h412v300h-412z"/>
+    <path
+        android:pathData="M384.18,300H27.82C12.53,300 0,287.17 0,271.52V28.48C0,12.83 12.53,0 27.82,0H384.29C399.47,0 412,12.83 412,28.48V271.63C412,287.17 399.47,300 384.18,300Z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M321.83,134.76V134C322.96,133.96 323.87,132.78 323.87,131.32V116.86C323.87,115.4 322.96,114.22 321.83,114.18V58.55C321.83,52.85 317.2,48.22 311.49,48.22H213.79C211.62,48.22 209.49,48.77 207.6,49.82C206.71,50.18 205.73,50.18 204.84,49.83L204.81,49.82C202.92,48.77 200.79,48.22 198.62,48.22H101.22C95.51,48.22 90.88,52.85 90.88,58.55V242.05C90.88,247.76 95.51,252.38 101.22,252.38H198.84C201,252.38 203.13,251.83 205.03,250.78C205.86,250.45 206.78,250.44 207.63,250.73L207.73,250.78C209.62,251.83 213.04,252.38 215.2,252.38H311.49C317.2,252.38 321.83,247.76 321.83,242.05V181.69C322.96,181.65 323.87,180.47 323.87,179.01V152.1C323.87,150.65 322.96,149.46 321.83,149.43V134.76ZM319.45,242.43C319.45,246.61 315.67,250.01 311.49,250.01H101.21C97.04,250.01 93.26,246.61 93.26,242.43V58.55C93.26,54.38 97.04,50.6 101.21,50.6H311.49C315.67,50.6 319.45,54.38 319.45,58.55V242.43Z"
+        android:fillColor="#DADCE0"/>
+    <path
+        android:pathData="M148,53L264,53A4,4 0,0 1,268 57L268,243A4,4 0,0 1,264 247L148,247A4,4 0,0 1,144 243L144,57A4,4 0,0 1,148 53z"
+        android:fillColor="#1A73E8"/>
+    <path
+        android:pathData="M150,57L262,57A2,2 0,0 1,264 59L264,241A2,2 0,0 1,262 243L150,243A2,2 0,0 1,148 241L148,59A2,2 0,0 1,150 57z"
+        android:fillColor="#ffffff"/>
+    <path
+        android:pathData="M164.48,237H154V226.42"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M154,237L169,222"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M247.52,63L258,63L258,73.58"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M258,63L243,78"
+        android:strokeLineJoin="round"
+        android:strokeWidth="4"
+        android:fillColor="#00000000"
+        android:strokeColor="#1A73E8"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/res/drawable/ic_battery_charger.xml b/res/drawable/ic_battery_charger.xml
new file mode 100644
index 0000000..4406a56
--- /dev/null
+++ b/res/drawable/ic_battery_charger.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960">
+    <path
+      android:fillColor="?android:attr/colorAccent"
+      android:pathData="M442,780L518,780L518,698L660,542L660,351Q660,351 660,351Q660,351 660,351L300,351Q300,351 300,351Q300,351 300,351L300,542L442,697.7L442,780ZM382,840L382,722L240,566L240,351Q240,326.25 257.63,308.63Q275.25,291 300,291L372,291L342,321L342,120L402,120L402,291L558,291L558,120L618,120L618,321L588,291L660,291Q684.75,291 702.38,308.63Q720,326.25 720,351L720,566L578,722L578,840L382,840ZM480,565L480,565L480,565L480,565Q480,565 480,565Q480,565 480,565L480,565Q480,565 480,565Q480,565 480,565L480,565L480,565L480,565L480,565Z"/>
+</vector>
+
diff --git a/res/drawable/ic_battery_tips_close.xml b/res/drawable/ic_battery_tips_close.xml
new file mode 100644
index 0000000..7ef571b
--- /dev/null
+++ b/res/drawable/ic_battery_tips_close.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/textColorSecondary"
+        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+</vector>
diff --git a/res/drawable/ic_battery_tips_close_icon.xml b/res/drawable/ic_battery_tips_close_icon.xml
new file mode 100644
index 0000000..b766474
--- /dev/null
+++ b/res/drawable/ic_battery_tips_close_icon.xml
@@ -0,0 +1,32 @@
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp">
+    <item>
+        <shape android:shape="oval">
+            <size
+                android:width="24dp"
+                android:height="24dp" />
+            <solid android:color="?android:attr/colorBackground" />
+        </shape>
+    </item>
+    <item android:drawable="@drawable/ic_battery_tips_close"
+        android:gravity="center"
+        android:width="16dp"
+        android:height="16dp"/>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_tips_lightbulb.xml b/res/drawable/ic_battery_tips_lightbulb.xml
new file mode 100644
index 0000000..f1449f9
--- /dev/null
+++ b/res/drawable/ic_battery_tips_lightbulb.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M7,20h4c0,1.1 -0.9,2 -2,2S7,21.1 7,20zM5,19h8v-2H5V19zM16.5,9.5c0,3.82 -2.66,5.86 -3.77,6.5H5.27C4.16,15.36 1.5,13.32 1.5,9.5C1.5,5.36 4.86,2 9,2S16.5,5.36 16.5,9.5zM14.5,9.5C14.5,6.47 12.03,4 9,4S3.5,6.47 3.5,9.5c0,2.47 1.49,3.89 2.35,4.5h6.3C13.01,13.39 14.5,11.97 14.5,9.5zM21.37,7.37L20,8l1.37,0.63L22,10l0.63,-1.37L24,8l-1.37,-0.63L22,6L21.37,7.37zM19,6l0.94,-2.06L22,3l-2.06,-0.94L19,0l-0.94,2.06L16,3l2.06,0.94L19,6z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_tips_thumb_down.xml b/res/drawable/ic_battery_tips_thumb_down.xml
new file mode 100644
index 0000000..cd7656b
--- /dev/null
+++ b/res/drawable/ic_battery_tips_thumb_down.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M242,120L686,120L686,632L408,920L369,889Q363,884 360,875Q357,866 357,853L357,843L402,632L103,632Q79,632 61,614Q43,596 43,572L43,490.16Q43,483 41.5,475.5Q40,468 43,461L169,171Q177.88,149.75 198.6,134.88Q219.31,120 242,120ZM626,180L229,180Q229,180 229,180Q229,180 229,180L103,479L103,572Q103,572 103,572Q103,572 103,572L476,572L423,821L626,607L626,180ZM626,607L626,607L626,572L626,572Q626,572 626,572Q626,572 626,572L626,479L626,180Q626,180 626,180Q626,180 626,180L626,180L626,607ZM686,632L686,572L819,572L819,180L686,180L686,120L879,120L879,632L686,632Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_battery_tips_thumb_up.xml b/res/drawable/ic_battery_tips_thumb_up.xml
new file mode 100644
index 0000000..b1d4cb6
--- /dev/null
+++ b/res/drawable/ic_battery_tips_thumb_up.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="?android:attr/colorAccent"
+        android:pathData="M716,840L272,840L272,328L550,40L589,71Q595,76 598,85Q601,94 601,107L601,117L556,328L855,328Q879,328 897,346Q915,364 915,388L915,469.84Q915,477 916.5,484.5Q918,492 915,499L789,789Q780.12,810.25 759.41,825.13Q738.69,840 716,840ZM332,780L729,780Q729,780 729,780Q729,780 729,780L855,481L855,388Q855,388 855,388Q855,388 855,388L482,388L535,139L332,353L332,780ZM332,353L332,353L332,388L332,388Q332,388 332,388Q332,388 332,388L332,481L332,780Q332,780 332,780Q332,780 332,780L332,780L332,353ZM272,328L272,388L139,388L139,780L272,780L272,840L79,840L79,328L272,328Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_lock_none.xml b/res/drawable/ic_lock_none.xml
index 31069b7..54b9bb4 100644
--- a/res/drawable/ic_lock_none.xml
+++ b/res/drawable/ic_lock_none.xml
@@ -18,7 +18,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="24"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
   <path
       android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h2c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"
       android:fillColor="?android:attr/colorAccent"/>
diff --git a/res/drawable/ic_lock_pin.xml b/res/drawable/ic_lock_pin.xml
index 587f49c..4614f53 100644
--- a/res/drawable/ic_lock_pin.xml
+++ b/res/drawable/ic_lock_pin.xml
@@ -18,7 +18,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="24"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
   <path
       android:pathData="M6,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM6,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM16,6c0,1.1 0.9,2 2,2s2,-0.9 2,-2 -0.9,-2 -2,-2 -2,0.9 -2,2zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"
       android:fillColor="?android:attr/colorAccent"/>
diff --git a/res/drawable/ic_lock_swipe.xml b/res/drawable/ic_lock_swipe.xml
index f7e78b8..fb8302d 100644
--- a/res/drawable/ic_lock_swipe.xml
+++ b/res/drawable/ic_lock_swipe.xml
@@ -18,7 +18,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="24"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal">
   <path
       android:pathData="M20.5,2v2.02C18.18,2.13 15.22,1 12,1S5.82,2.13 3.5,4.02V2H2v3.5V7h1.5H7V5.5H4.09c2.11,-1.86 4.88,-3 7.91,-3s5.79,1.14 7.91,3H17V7h3.5H22V5.5V2H20.5z"
       android:fillColor="?android:attr/colorAccent"/>
diff --git a/res/drawable/ic_password.xml b/res/drawable/ic_password.xml
index 341e544..cf3b408 100644
--- a/res/drawable/ic_password.xml
+++ b/res/drawable/ic_password.xml
@@ -18,7 +18,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
+    android:viewportWidth="24.0"
+    android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="?android:attr/colorAccent"
         android:pathData="M21.5,9.39l-1.63,0l0.81,-1.42l-0.86,-0.5l-0.82,1.42l-0.82,-1.42l-0.86,0.5l0.81,1.42l-1.63,0l0,1l1.63,0l-0.81,1.41l0.86,0.5l0.82,-1.41l0.82,1.41l0.86,-0.5l-0.81,-1.41l1.63,0z" />
diff --git a/res/drawable/ic_pattern.xml b/res/drawable/ic_pattern.xml
index 788eaa7..e56fb00 100644
--- a/res/drawable/ic_pattern.xml
+++ b/res/drawable/ic_pattern.xml
@@ -18,7 +18,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
+    android:viewportWidth="24.0"
+    android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="?android:attr/colorAccent"
         android:pathData="M4,4m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
diff --git a/res/drawable/ic_pin.xml b/res/drawable/ic_pin.xml
index 682e934..8520ec1 100644
--- a/res/drawable/ic_pin.xml
+++ b/res/drawable/ic_pin.xml
@@ -18,7 +18,8 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportHeight="24.0"
-    android:viewportWidth="24.0">
+    android:viewportWidth="24.0"
+    android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="?android:attr/colorAccent"
         android:pathData="M20,4L4,4A2,2 0,0 0,2 6L2,18a2,2 0,0 0,2 2L20,20a2,2 0,0 0,2 -2L22,6A2,2 0,0 0,20 4ZM7.1,15L5.9,15L5.9,10.2L4.7,10.2L4.7,9L7.1,9v6ZM13.2,11.4A1.2,1.2 0,0 1,12 12.6L10.8,12.6v1.2h2.4L13.2,15L9.6,15L9.6,12.6a1.2,1.2 0,0 1,1.2 -1.2L12,11.4L12,10.2L9.6,10.2L9.6,9L12,9a1.2,1.2 0,0 1,1.2 1.2v1.2ZM19.3,11.1a0.9,0.9 0,0 1,-0.9 0.9,0.9 0.9,0 0,1 0.9,0.9v0.9A1.2,1.2 0,0 1,18.1 15L15.7,15L15.7,13.8h2.4L18.1,12.6L16.9,12.6L16.9,11.4h1.2L18.1,10.2L15.7,10.2L15.7,9h2.4a1.2,1.2 0,0 1,1.2 1.2v0.9Z" />
diff --git a/res/layout-land/choose_lock_pattern_common.xml b/res/layout-land/choose_lock_pattern_common.xml
index 2913c5a..e440461 100644
--- a/res/layout-land/choose_lock_pattern_common.xml
+++ b/res/layout-land/choose_lock_pattern_common.xml
@@ -38,15 +38,6 @@
         android:paddingRight="0dp"
         android:paddingBottom="0dp">
 
-        <!-- TODO b/249974175 Move into Glif header mixin -->
-        <Button
-            android:id="@+id/screen_lock_options"
-            style="@style/SudGlifButton.Tertiary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/setup_lock_settings_options_button_label"
-            android:visibility="gone"/>
-
         <com.google.android.setupdesign.view.FillContentLayout
             style="@style/LockPatternContainerStyle"
             android:layout_width="wrap_content"
diff --git a/res/layout/action_button.xml b/res/layout/action_button.xml
new file mode 100644
index 0000000..00fdc1e
--- /dev/null
+++ b/res/layout/action_button.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="center_vertical|start"
+    android:paddingStart="12dp"
+    android:paddingEnd="12dp"
+    android:drawablePadding="8dp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:textSize="12sp"
+    android:maxWidth="192dp"
+    android:singleLine="true"
+    android:clickable="true"
+    android:background="@drawable/action_button_bg"
+    android:drawableTint="?android:attr/textColorPrimary"
+    android:drawableTintMode="src_in"
+    style="?android:attr/borderlessButtonStyle"
+    />
+
diff --git a/res/layout/battery_tips_card.xml b/res/layout/battery_tips_card.xml
new file mode 100644
index 0000000..dde559d
--- /dev/null
+++ b/res/layout/battery_tips_card.xml
@@ -0,0 +1,114 @@
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/battery_tips_card"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <LinearLayout
+        android:id="@+id/tips_card"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/battery_tips_all_rounded_bg_ripple"
+        android:orientation="vertical"
+        android:padding="24dp">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|start"
+            android:src="@drawable/ic_battery_tips_lightbulb" />
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:textAlignment="viewStart"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginTop="8dp"
+            android:gravity="end">
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/dismiss_button"
+                style="@style/Widget.Material3.Button.TextButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end|center_vertical"
+                android:paddingHorizontal="16dp"
+                android:layout_marginEnd="8dp"
+                android:text="@string/battery_tips_card_dismiss_button"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="?android:attr/colorAccent"
+                android:textStyle="bold" />
+
+            <com.google.android.material.button.MaterialButton
+                android:id="@+id/main_button"
+                style="@style/Widget.Material3.Button.OutlinedButton"
+                android:paddingHorizontal="16dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end|center_vertical"
+                android:text="@string/battery_tips_card_action_button"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="?android:attr/textColorPrimary"
+                android:textStyle="bold"
+                app:strokeColor="?android:attr/colorAccent"
+                app:strokeWidth="1dp" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <Space
+        android:layout_width="0dp"
+        android:layout_height="1dp"/>
+
+    <LinearLayout
+        android:id="@+id/feedback_card"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/battery_tips_half_rounded_bottom_bg"
+        android:gravity="center_vertical|start"
+        android:orientation="horizontal"
+        android:paddingHorizontal="24dp"
+        android:paddingVertical="16dp"
+        android:visibility="gone">
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="0dp"
+            android:layout_marginEnd="20dp"
+            android:layout_weight="1"
+            android:text="@string/battery_tips_card_feedback_info"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textStyle="bold"/>
+
+        <ImageButton
+            android:id="@+id/thumb_up"
+            style="@style/Banner.Dismiss.SettingsLib"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|end"
+            android:layout_marginEnd="20dp"
+            android:src="@drawable/ic_battery_tips_thumb_up" />
+
+        <ImageButton
+            android:id="@+id/thumb_down"
+            style="@style/Banner.Dismiss.SettingsLib"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|end"
+            android:src="@drawable/ic_battery_tips_thumb_down" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/choose_lock_password.xml b/res/layout/choose_lock_password.xml
index 5819774..c2eb13a 100644
--- a/res/layout/choose_lock_password.xml
+++ b/res/layout/choose_lock_password.xml
@@ -61,12 +61,6 @@
                 android:imeOptions="actionNext|flagNoExtractUi|flagForceAscii"
                 style="@style/TextAppearance.PasswordEntry"/>
 
-            <androidx.recyclerview.widget.RecyclerView
-                android:layout_marginTop="8dp"
-                android:id="@+id/password_requirements_view"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"/>
-
             <CheckBox
                 android:id="@+id/auto_pin_confirm_enabler"
                 android:layout_marginTop="8dp"
@@ -91,14 +85,6 @@
             android:textSize="16sp"
             android:visibility="gone" />
 
-        <Button
-            android:id="@+id/screen_lock_options"
-            style="@style/SudGlifButton.Tertiary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/setup_lock_settings_options_button_label"
-            android:visibility="gone" />
-
     </LinearLayout>
 
 </com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout/choose_lock_pattern_common.xml b/res/layout/choose_lock_pattern_common.xml
index 774f5cd..ddfa046 100644
--- a/res/layout/choose_lock_pattern_common.xml
+++ b/res/layout/choose_lock_pattern_common.xml
@@ -36,14 +36,6 @@
         android:paddingLeft="0dp"
         android:paddingRight="0dp">
 
-        <Button
-            android:id="@+id/screen_lock_options"
-            style="@style/LockPatternButtonStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/setup_lock_settings_options_button_label"
-            android:visibility="gone"/>
-
         <com.google.android.setupdesign.view.FillContentLayout
             style="@style/LockPatternContainerStyle"
             android:layout_width="wrap_content"
diff --git a/res/layout/layout_color_selector.xml b/res/layout/layout_color_selector.xml
index c366add..a6b9cc8 100644
--- a/res/layout/layout_color_selector.xml
+++ b/res/layout/layout_color_selector.xml
@@ -14,161 +14,167 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/color_selector_root_view"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeight"
-    android:orientation="vertical">
-
+    android:padding="20dp"
+    android:clipToPadding="false"
+    android:scrollbarStyle="outsideOverlay">
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:orientation="horizontal">
-
-        <RadioButton
-            android:id="@+id/color_radio_button_00"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_01_selector"
-            android:contentDescription="@string/screen_flash_color_blue" />
-
-        <Space
-            android:layout_width="0dp"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:orientation="vertical">
+        <LinearLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_weight="1" />
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
 
-        <RadioButton
-            android:id="@+id/color_radio_button_01"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_02_selector"
-            android:contentDescription="@string/screen_flash_color_azure" />
+            <RadioButton
+                android:id="@+id/color_radio_button_00"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_01_selector"
+                android:contentDescription="@string/screen_flash_color_blue" />
 
-        <Space
-            android:layout_width="0dp"
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_01"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_02_selector"
+                android:contentDescription="@string/screen_flash_color_azure" />
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_02"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_03_selector"
+                android:contentDescription="@string/screen_flash_color_cyan" />
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_03"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_04_selector"
+                android:contentDescription="@string/screen_flash_color_spring_green" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_weight="1" />
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
 
-        <RadioButton
-            android:id="@+id/color_radio_button_02"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_03_selector"
-            android:contentDescription="@string/screen_flash_color_cyan" />
+            <RadioButton
+                android:id="@+id/color_radio_button_04"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_05_selector"
+                android:contentDescription="@string/screen_flash_color_green" />
 
-        <Space
-            android:layout_width="0dp"
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_05"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_06_selector"
+                android:contentDescription="@string/screen_flash_color_chartreuse_green" />
+
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+            <RadioButton
+                android:id="@+id/color_radio_button_06"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_07_selector"
+                android:contentDescription="@string/screen_flash_color_yellow" />
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_07"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_08_selector"
+                android:contentDescription="@string/screen_flash_color_orange" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_weight="1" />
+            android:layout_marginBottom="10dp"
+            android:orientation="horizontal">
 
-        <RadioButton
-            android:id="@+id/color_radio_button_03"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_04_selector"
-            android:contentDescription="@string/screen_flash_color_spring_green" />
+            <RadioButton
+                android:id="@+id/color_radio_button_08"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_09_selector"
+                android:contentDescription="@string/screen_flash_color_red" />
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_09"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_10_selector"
+                android:contentDescription="@string/screen_flash_color_rose" />
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_10"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_11_selector"
+                android:contentDescription="@string/screen_flash_color_magenta" />
+
+            <Space
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+
+            <RadioButton
+                android:id="@+id/color_radio_button_11"
+                android:layout_width="@dimen/screen_flash_color_button_frame_size"
+                android:layout_height="@dimen/screen_flash_color_button_frame_size"
+                android:button="@drawable/screen_flash_color_12_selector"
+                android:contentDescription="@string/screen_flash_color_violet" />
+
+        </LinearLayout>
     </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:orientation="horizontal">
-
-        <RadioButton
-            android:id="@+id/color_radio_button_04"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_05_selector"
-            android:contentDescription="@string/screen_flash_color_green" />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <RadioButton
-            android:id="@+id/color_radio_button_05"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_06_selector"
-            android:contentDescription="@string/screen_flash_color_chartreuse_green" />
-
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-        <RadioButton
-            android:id="@+id/color_radio_button_06"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_07_selector"
-            android:contentDescription="@string/screen_flash_color_yellow" />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <RadioButton
-            android:id="@+id/color_radio_button_07"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_08_selector"
-            android:contentDescription="@string/screen_flash_color_orange" />
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="10dp"
-        android:orientation="horizontal">
-
-        <RadioButton
-            android:id="@+id/color_radio_button_08"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_09_selector"
-            android:contentDescription="@string/screen_flash_color_red" />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <RadioButton
-            android:id="@+id/color_radio_button_09"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_10_selector"
-            android:contentDescription="@string/screen_flash_color_rose" />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <RadioButton
-            android:id="@+id/color_radio_button_10"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_11_selector"
-            android:contentDescription="@string/screen_flash_color_magenta" />
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <RadioButton
-            android:id="@+id/color_radio_button_11"
-            android:layout_width="@dimen/screen_flash_color_button_frame_size"
-            android:layout_height="@dimen/screen_flash_color_button_frame_size"
-            android:button="@drawable/screen_flash_color_12_selector"
-            android:contentDescription="@string/screen_flash_color_violet" />
-
-    </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</ScrollView>
diff --git a/res/layout/layout_color_selector_dialog.xml b/res/layout/layout_color_selector_dialog.xml
index 70d4509..e107689 100644
--- a/res/layout/layout_color_selector_dialog.xml
+++ b/res/layout/layout_color_selector_dialog.xml
@@ -17,16 +17,12 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:paddingBottom="24dp">
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
 
     <com.android.settings.accessibility.ColorSelectorLayout
         android:id="@+id/color_selector_preference"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="2dp"
-        android:layout_marginHorizontal="25dp"
-        android:layout_marginTop="21dp"
-        android:orientation="vertical" />
+        android:layout_height="wrap_content"/>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml
index 5c1db15..da1eb62 100644
--- a/res/layout/locale_order_list.xml
+++ b/res/layout/locale_order_list.xml
@@ -27,11 +27,11 @@
         android:clipChildren="true"
         android:orientation="vertical">
 
-        <com.android.settings.localepicker.LocaleRecyclerView
+        <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/dragList"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:scrollbars="vertical"/>
+            android:scrollbars="none"/>
 
         <Button
             android:id="@+id/add_language"
diff --git a/res/layout/modifier_key_item.xml b/res/layout/modifier_key_item.xml
index a189479..683f631 100644
--- a/res/layout/modifier_key_item.xml
+++ b/res/layout/modifier_key_item.xml
@@ -19,8 +19,7 @@
     android:layout_marginTop="8dip"
     android:layout_marginBottom="8dip"
     android:minHeight="?android:attr/listPreferredItemHeight"
-    android:paddingEnd="?android:attr/scrollbarSize"
-    android:layout_weight="1">
+    android:paddingEnd="?android:attr/scrollbarSize">
 
     <ImageView
         android:id="@+id/modifier_key_check_icon"
@@ -36,7 +35,7 @@
 
     <TextView
         android:id="@+id/modifier_key_text"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerVertical="true"
         android:textDirection="locale"
@@ -46,4 +45,38 @@
         android:ellipsize="marquee"
         android:fadingEdge="horizontal" />
 
+    <TextView
+        android:id="@+id/modifier_key_left_bracket"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:textDirection="locale"
+        android:padding="1dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_toEndOf="@+id/modifier_key_text"
+        android:fadingEdge="horizontal" />
+
+    <ImageView
+        android:id="@+id/modifier_key_action_key_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@+id/modifier_key_left_bracket"
+        android:fadingEdge="horizontal"
+        android:tint="?android:attr/textColorPrimary"/>
+
+    <TextView
+        android:id="@+id/modifier_key_right_bracket"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:textDirection="locale"
+        android:padding="1dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_toEndOf="@+id/modifier_key_action_key_icon"
+        android:fadingEdge="horizontal" />
+
+    <View android:layout_width="wrap_content"
+          android:layout_height="match_parent" />
+
 </RelativeLayout>
diff --git a/res/layout/modifier_keys_custom_key.xml b/res/layout/modifier_keys_custom_key.xml
new file mode 100644
index 0000000..f390c00
--- /dev/null
+++ b/res/layout/modifier_keys_custom_key.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground">
+
+    <FrameLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <androidx.preference.internal.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:maxWidth="48dp"
+            app:maxHeight="48dp" />
+    </FrameLayout>
+
+    <RelativeLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:layout_weight="1">
+
+        <TextView android:id="@+id/title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:textAppearance="?android:attr/textAppearanceLarge"
+                  android:textColor="?android:attr/textColorPrimary"
+                  android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+id/summary"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_below="@+id/title"
+                  android:layout_alignStart="@+id/title"
+                  android:layout_alignLeft="@+id/title"
+                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:textColor="?android:attr/textColorSecondary"
+                  android:maxLines="4" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="15dp"
+            android:layout_toEndOf="@+id/title"
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/modifier_key_left_bracket"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textDirection="locale"
+                android:paddingStart="1dp"
+                android:paddingEnd="1dp"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:textColor="?android:attr/textColorPrimary"
+                android:fadingEdge="horizontal" />
+
+            <ImageView
+                android:id="@+id/modifier_key_action_key_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fadingEdge="horizontal"
+                android:tint="?android:attr/textColorPrimary"/>
+
+            <TextView
+                android:id="@+id/modifier_key_right_bracket"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textDirection="locale"
+                android:paddingStart="1dp"
+                android:paddingEnd="1dp"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:textColor="?android:attr/textColorPrimary"
+                android:fadingEdge="horizontal" />
+        </LinearLayout>
+    </RelativeLayout>
+
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout android:id="@android:id/widget_frame"
+                  android:layout_width="wrap_content"
+                  android:layout_height="match_parent"
+                  android:gravity="center_vertical"
+                  android:orientation="vertical" />
+</LinearLayout>
diff --git a/res/layout/preference_check_icon.xml b/res/layout/preference_check_icon.xml
index 1b759fc..bd0dd79 100644
--- a/res/layout/preference_check_icon.xml
+++ b/res/layout/preference_check_icon.xml
@@ -20,4 +20,5 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_centerVertical="true"
-    android:layout_marginHorizontal="16dp"/>
\ No newline at end of file
+    android:layout_marginHorizontal="16dp"
+    android:contentDescription="@*android:string/checked"/>
\ No newline at end of file
diff --git a/res/layout/radio_with_image_preference.xml b/res/layout/radio_with_image_preference.xml
new file mode 100644
index 0000000..fcd0e26
--- /dev/null
+++ b/res/layout/radio_with_image_preference.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:gravity="center_vertical"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingTop="16dp"
+    android:paddingBottom="16dp">
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingHorizontal="20dp"
+        android:gravity="center_horizontal"
+        android:minWidth="56dp"
+        android:orientation="vertical"/>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+        <LinearLayout
+            android:id="@+id/summary_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone">
+            <TextView
+                android:id="@android:id/summary"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textAlignment="viewStart"
+                android:textColor="?android:attr/textColorSecondary"/>
+        </LinearLayout>
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/settingslib_illustration_padding"
+            android:adjustViewBounds="true"
+            android:maxWidth="@dimen/settingslib_illustration_width"
+            android:maxHeight="@dimen/settingslib_illustration_height" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/raw/user_aspect_ratio_education.json b/res/raw/user_aspect_ratio_education.json
new file mode 100644
index 0000000..ab74b45
--- /dev/null
+++ b/res/raw/user_aspect_ratio_education.json
@@ -0,0 +1 @@
+{"v":"5.12.0","fr":60,"ip":0,"op":226,"w":412,"h":300,"nm":"AppCompat_Felix_DT","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[38.5,-79.5,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.661,-79.5,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.181,-79.5,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.183,-79.5,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.974,-79.5,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.7,-79.5,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.943,-79.5,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[51.81,-79.5,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[52.893,-79.5,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.628,-79.5,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.171,-79.5,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.59,-79.5,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.925,-79.5,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.197,-79.5,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.423,-79.5,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.611,-79.5,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.77,-79.5,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.904,-79.5,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.017,-79.5,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.113,-79.5,0],"t":34,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.195,-79.5,0],"t":35,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.263,-79.5,0],"t":36,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.32,-79.5,0],"t":37,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.368,-79.5,0],"t":38,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.406,-79.5,0],"t":39,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.461,-79.5,0],"t":41,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.5,-79.5,0],"t":60,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.823,-79.5,0],"t":61,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.861,-79.5,0],"t":62,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.865,-79.5,0],"t":63,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.449,-79.5,0],"t":64,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[70.9,-79.5,0],"t":65,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[79.385,-79.5,0],"t":66,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[83.121,-79.5,0],"t":67,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[85.286,-79.5,0],"t":68,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[86.757,-79.5,0],"t":69,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[87.841,-79.5,0],"t":70,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[88.68,-79.5,0],"t":71,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[89.349,-79.5,0],"t":72,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[89.894,-79.5,0],"t":73,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[90.345,-79.5,0],"t":74,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[90.722,-79.5,0],"t":75,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[91.039,-79.5,0],"t":76,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[91.307,-79.5,0],"t":77,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[91.534,-79.5,0],"t":78,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[91.727,-79.5,0],"t":79,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[91.889,-79.5,0],"t":80,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.026,-79.5,0],"t":81,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.141,-79.5,0],"t":82,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.236,-79.5,0],"t":83,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.313,-79.5,0],"t":84,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.375,-79.5,0],"t":85,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.458,-79.5,0],"t":87,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.5,-79.5,0],"t":105,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[92.177,-79.5,0],"t":106,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[91.139,-79.5,0],"t":107,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[89.135,-79.5,0],"t":108,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[85.551,-79.5,0],"t":109,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[78.1,-79.5,0],"t":110,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[69.615,-79.5,0],"t":111,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[65.879,-79.5,0],"t":112,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[63.714,-79.5,0],"t":113,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[62.243,-79.5,0],"t":114,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[61.159,-79.5,0],"t":115,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[60.32,-79.5,0],"t":116,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.651,-79.5,0],"t":117,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[59.106,-79.5,0],"t":118,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[58.655,-79.5,0],"t":119,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[58.278,-79.5,0],"t":120,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.961,-79.5,0],"t":121,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.693,-79.5,0],"t":122,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.466,-79.5,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.273,-79.5,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[57.111,-79.5,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.974,-79.5,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.859,-79.5,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.764,-79.5,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.687,-79.5,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.625,-79.5,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.577,-79.5,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.542,-79.5,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.5,-79.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[56.339,-79.5,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[55.819,-79.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[54.817,-79.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[53.026,-79.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[49.3,-79.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[45.057,-79.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[43.19,-79.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[42.107,-79.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[41.372,-79.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.829,-79.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.41,-79.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[40.075,-79.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.803,-79.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.577,-79.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.389,-79.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.23,-79.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[39.096,-79.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.983,-79.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.887,-79.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.805,-79.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.737,-79.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.68,-79.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.632,-79.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.594,-79.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.563,-79.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.539,-79.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[38.502,-79.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[7.5,3.077],[7.5,-7.5],[-2.981,-7.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-7.5,7.5],[7.5,-7.5]],"c":false},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2916,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[-38.5,79.5,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.661,79.5,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.181,79.5,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.183,79.5,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.974,79.5,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-45.7,79.5,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-49.943,79.5,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-51.81,79.5,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-52.893,79.5,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.628,79.5,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.171,79.5,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.59,79.5,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.925,79.5,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.197,79.5,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.423,79.5,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.611,79.5,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.77,79.5,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.904,79.5,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.017,79.5,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.113,79.5,0],"t":34,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.195,79.5,0],"t":35,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.263,79.5,0],"t":36,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.32,79.5,0],"t":37,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.368,79.5,0],"t":38,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.406,79.5,0],"t":39,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.461,79.5,0],"t":41,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.5,79.5,0],"t":60,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.823,79.5,0],"t":61,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.861,79.5,0],"t":62,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.865,79.5,0],"t":63,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.449,79.5,0],"t":64,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-70.9,79.5,0],"t":65,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-79.385,79.5,0],"t":66,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-83.121,79.5,0],"t":67,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-85.286,79.5,0],"t":68,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-86.757,79.5,0],"t":69,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-87.841,79.5,0],"t":70,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-88.68,79.5,0],"t":71,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-89.349,79.5,0],"t":72,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-89.894,79.5,0],"t":73,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-90.345,79.5,0],"t":74,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-90.722,79.5,0],"t":75,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-91.039,79.5,0],"t":76,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-91.307,79.5,0],"t":77,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-91.534,79.5,0],"t":78,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-91.727,79.5,0],"t":79,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-91.889,79.5,0],"t":80,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.026,79.5,0],"t":81,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.141,79.5,0],"t":82,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.236,79.5,0],"t":83,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.313,79.5,0],"t":84,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.375,79.5,0],"t":85,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.458,79.5,0],"t":87,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.5,79.5,0],"t":105,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-92.177,79.5,0],"t":106,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-91.139,79.5,0],"t":107,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-89.135,79.5,0],"t":108,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-85.551,79.5,0],"t":109,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-78.1,79.5,0],"t":110,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-69.615,79.5,0],"t":111,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-65.879,79.5,0],"t":112,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-63.714,79.5,0],"t":113,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-62.243,79.5,0],"t":114,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-61.159,79.5,0],"t":115,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-60.32,79.5,0],"t":116,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.651,79.5,0],"t":117,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-59.106,79.5,0],"t":118,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.655,79.5,0],"t":119,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-58.278,79.5,0],"t":120,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.961,79.5,0],"t":121,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.693,79.5,0],"t":122,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.466,79.5,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.273,79.5,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-57.111,79.5,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.974,79.5,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.859,79.5,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.764,79.5,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.687,79.5,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.625,79.5,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.577,79.5,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.542,79.5,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.5,79.5,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.339,79.5,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-55.819,79.5,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-54.817,79.5,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-53.026,79.5,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-49.3,79.5,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-45.057,79.5,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-43.19,79.5,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-42.107,79.5,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-41.372,79.5,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.829,79.5,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.41,79.5,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40.075,79.5,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.803,79.5,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.577,79.5,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.389,79.5,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.23,79.5,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-39.096,79.5,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.983,79.5,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.887,79.5,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.805,79.5,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.737,79.5,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.68,79.5,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.632,79.5,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.594,79.5,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.563,79.5,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.539,79.5,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-38.502,79.5,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-7.5,-3.077],[-7.5,7.5],[2.981,7.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[7.5,-7.5],[-7.5,7.5]],"c":false},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2916,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":15,"s":[104,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":20,"s":[118.4,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":45,"s":[140,186]},{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":60,"s":[140,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":65,"s":[168.8,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":90,"s":[212,186]},{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":105,"s":[212,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":110,"s":[183.2,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":135,"s":[140,186]},{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":150,"s":[140,186]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":155,"s":[125.6,186]},{"t":180,"s":[104,186]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"inside","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2916,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":15,"s":[112,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":20,"s":[126.4,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":45,"s":[148,194]},{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":60,"s":[148,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":65,"s":[176.8,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":90,"s":[220,194]},{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":105,"s":[220,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":110,"s":[191.2,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":135,"s":[148,194]},{"i":{"x":[0.8,0.8],"y":[0.15,1]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":150,"s":[148,194]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0]},"t":155,"s":[133.6,194]},{"t":180,"s":[112,194]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":4,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"outside","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2916,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[207.5,150.303,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.133,-0.038],[0,0],[0,0],[0,-1.46],[0,0],[1.133,-0.038],[0,0],[5.707,0],[0,0],[1.894,1.051],[0,0],[0.833,-0.334],[2.166,0],[0,0],[0,5.707],[0,0],[-5.707,0],[0,0],[-1.894,-1.05],[0,0],[-0.883,0.354],[-2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0]],"o":[[0,0],[0,0],[1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.706],[0,0],[-2.166,0],[0,0],[-0.846,-0.289],[-1.894,1.051],[0,0],[-5.707,0],[0,0],[0,-5.707],[0,0],[2.166,0],[0,0],[0.886,0.346],[1.894,-1.05],[0,0],[5.707,0],[0,0],[1.133,0.038],[0,0],[0,1.46]],"v":[[114.45,-16.3],[114.45,-15.539],[114.45,-0.877],[116.494,1.802],[116.494,28.704],[114.45,31.383],[114.45,91.749],[104.117,102.082],[7.828,102.082],[0.351,100.48],[0.25,100.424],[-2.349,100.48],[-8.539,102.082],[-106.16,102.082],[-116.494,91.748],[-116.494,-91.748],[-106.16,-102.082],[-8.754,-102.082],[-2.563,-100.48],[-2.532,-100.468],[0.221,-100.48],[6.411,-102.082],[104.116,-102.082],[114.45,-91.748],[114.45,-36.119],[116.494,-33.44],[116.494,-18.979]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,4.177],[0,0],[4.177,0],[0,0],[0,-4.177],[0,0],[-4.177,0]],"o":[[4.177,0],[0,0],[0,-4.177],[0,0],[-4.177,0],[0,0],[0,4.177],[0,0]],"v":[[104.117,99.704],[112.072,92.128],[112.072,-91.748],[104.117,-99.704],[-106.161,-99.704],[-114.116,-91.748],[-114.116,92.128],[-106.161,99.704]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"felix","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2916,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.291,0],[0,0],[0,15.544],[0,0],[15.185,0],[0,0],[0,-15.652],[0,0]],"o":[[0,0],[15.291,0],[0,0],[0,-15.652],[0,0],[-15.291,0],[0,0],[0,15.652]],"v":[[-178.179,150],[178.179,150],[206,121.63],[206,-121.522],[178.286,-150],[-178.179,-150],[-206,-121.522],[-206,121.522]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":".white","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"cl":"white"}],"ip":0,"op":2916,"st":0,"ct":1,"bm":0}],"markers":[{"tm":195,"cm":"Plus .5s hold for loop","dr":30}],"props":{}}
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 787163e..552ca3f 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1409,4 +1409,20 @@
     <integer-array name="network_mode_3g_deprecated_carrier_id" translatable="false">
     </integer-array>
 
+    <!-- The following 3 arrays are for power anomaly tips card. Please keep them the same size. -->
+    <string-array name="power_anomaly_titles">
+        <item>Turn on adaptive brightness to extend battery life</item>
+        <item>Reduce screen timeout to extend battery life</item>
+    </string-array>
+
+    <string-array name="power_anomaly_main_btn_strings">
+        <item>@string/battery_tips_card_action_button</item>
+        <item>@string/battery_tips_card_action_button</item>
+    </string-array>
+
+    <string-array name="power_anomaly_dismiss_btn_strings">
+        <item>@string/battery_tips_card_dismiss_button</item>
+        <item>@string/battery_tips_card_dismiss_button</item>
+    </string-array>
+
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 5ae0220..687fa15 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -548,6 +548,9 @@
     <!-- Whether to show Smooth Display feature in Settings Options -->
     <bool name="config_show_smooth_display">false</bool>
 
+    <!-- Whether to show Stay awake on fold feature in Settings Options -->
+    <bool name="config_stay_awake_on_fold">false</bool>
+
     <!-- Whether to show emergency settings in top-level Settings -->
     <bool name="config_show_emergency_settings">true</bool>
 
@@ -608,6 +611,31 @@
         <item>3</item>
     </integer-array>
 
+    <!-- App aspect ratio settings screen, user aspect ratio override options. Must be the same
+         length and order as config_userAspectRatioOverrideValues below. -->
+    <string-array name="config_userAspectRatioOverrideEntries" translatable="false">
+        <item>@string/user_aspect_ratio_app_default</item>
+        <item>@string/user_aspect_ratio_fullscreen</item>
+        <item>@string/user_aspect_ratio_half_screen</item>
+        <item>@string/user_aspect_ratio_device_size</item>
+        <item>@string/user_aspect_ratio_16_9</item>
+        <item>@string/user_aspect_ratio_4_3</item>
+        <item>@string/user_aspect_ratio_3_2</item>
+    </string-array>
+
+    <!-- App aspect ratio settings screen, user aspect ratio override options. Must be the same
+         length and order as config_userAspectRatioOverrideEntries above. The values must
+         correspond to PackageManager.UserMinAspectRatio -->
+    <integer-array name="config_userAspectRatioOverrideValues" translatable="false">
+        <item>0</item> <!-- USER_MIN_ASPECT_RATIO_UNSET -->
+        <item>6</item> <!-- USER_MIN_ASPECT_RATIO_FULLSCREEN -->
+        <item>1</item> <!-- USER_MIN_ASPECT_RATIO_SPLIT_SCREEN -->
+        <item>2</item> <!-- USER_MIN_ASPECT_RATIO_DISPLAY_SIZE -->
+        <item>4</item> <!-- USER_MIN_ASPECT_RATIO_16_9 -->
+        <item>3</item> <!-- USER_MIN_ASPECT_RATIO_4_3 -->
+        <item>5</item> <!-- USER_MIN_ASPECT_RATIO_3_2 -->
+    </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
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index de33ec7..fd582de 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -229,6 +229,15 @@
     <!-- Minimum height for setting a lock pattern -->
     <dimen name="choose_lockscreen_min_height">200dp</dimen>
 
+    <!-- Choose lock Password requirement dimensions -->
+    <dimen name="password_requirement_view_margin_top">16dp</dimen>
+
+    <!-- Screen lock option button dimensions -->
+    <dimen name="screen_lock_options_button_margin_top">32dp</dimen>
+
+    <!-- Choose lock Password requirement font size -->
+    <dimen name="password_requirement_font_size">16sp</dimen>
+
     <!-- Select dialog -->
     <dimen name="select_dialog_padding_start">20dp</dimen>
     <dimen name="select_dialog_item_margin_start">12dp</dimen>
@@ -364,6 +373,10 @@
     <dimen name="chartview_trapezoid_margin_start">1dp</dimen>
     <dimen name="chartview_trapezoid_margin_bottom">2dp</dimen>
 
+    <!-- Battery tips card view component -->
+    <dimen name="battery_tips_card_corner_radius_small">4dp</dimen>
+    <dimen name="battery_tips_card_corner_radius_normal">24dp</dimen>
+
     <!-- Dimensions for Dream settings cards -->
     <dimen name="dream_item_min_column_width">174dp</dimen>
     <dimen name="dream_item_corner_radius">28dp</dimen>
@@ -397,6 +410,9 @@
     <!-- Margin for SD card setup completion Image -->
     <dimen name="setup_completion_margin_top">88dp</dimen>
 
+    <!-- QR code action button -->
+    <dimen name="action_button_icon_size">18dp</dimen>
+
     <!-- Biometrics Face enroll education dimensions-->
     <dimen name="face_enroll_icon_large_width">300dp</dimen>
     <dimen name="face_enroll_icon_large_height">300dp</dimen>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index c1cfe2e..efd1791 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -40,4 +40,8 @@
 
     <!-- For a layout container to add AppLocaleDetails into -->
     <item type="id" name="layout_app_locale_details" />
+
+    <!-- For screen lock options button -->
+    <item type="id" name="screen_lock_options" />
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1ad45d5..468f3fa 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -258,8 +258,10 @@
 
     <!-- Title for stylus device details page [CHAR LIMIT=50] -->
     <string name="stylus_device_details_title">Stylus</string>
-    <!-- Preference title for setting the default note taking app [CHAR LIMIT=none] -->
-    <string name="stylus_default_notes_app">Default notes app</string>
+    <!-- Preference title for setting the app that opens user presses stylus button [CHAR LIMIT=none] -->
+    <string name="stylus_default_notes_app">Tail button press</string>
+    <!-- Summary for the app that opens when user presses stylus tail button, if set to a work profile app [CHAR LIMIT=none] -->
+    <string name="stylus_default_notes_summary_work"><xliff:g id="app_name" example="Mail">%s</xliff:g> (Work profile)</string>
     <!-- Preference title for toggling whether handwriting in textfields is enabled [CHAR LIMIT=none] -->
     <string name="stylus_textfield_handwriting">Write in text fields</string>
     <!-- Preference title for toggling whether stylus button presses are ignored [CHAR LIMIT=none] -->
@@ -411,7 +413,7 @@
     <!-- The title of the menu entry of Numbers system preference. [CHAR LIMIT=50]  -->
     <string name="numbers_preferences_title">Numbers preferences</string>
     <!-- The summary of default string for each regional preference. [CHAR LIMIT=50] -->
-    <string name="default_string_of_regional_preference">Use app default</string>
+    <string name="default_string_of_regional_preference">Use default</string>
     <!-- The title of Celsius for preference of temperature unit. [CHAR LIMIT=50] -->
     <string name="celsius_temperature_unit">Celsius (\u00B0C)</string>
     <!-- The title of Fahrenheit for preference of temperature unit. [CHAR LIMIT=50] -->
@@ -863,7 +865,7 @@
 
     <!-- Biometric settings --><skip />
     <!-- Title shown for menu item that launches biometric settings. [CHAR LIMIT=66] -->
-    <string name="security_settings_biometric_preference_title">Face &amp; Fingerprint Unlock</string>
+    <string name="security_settings_biometric_preference_title">Fingerprint &amp; Face Unlock</string>
     <!-- Title shown for work menu item that launches biometric settings. [CHAR LIMIT=66] -->
     <string name="security_settings_work_biometric_preference_title">Face &amp; Fingerprint Unlock for work</string>
     <!-- Message shown in summary field of biometric settings. [CHAR LIMIT=66] -->
@@ -1215,14 +1217,8 @@
     <!-- Title for preference that guides the user to skip Face Unlock setup [CHAR LIMIT=60]-->
     <string name="face_unlock_skip_face">Continue without Face Unlock</string>
 
-    <!-- Title for preference that guides the user through creating a backup unlock pattern for biometrics unlock [CHAR LIMIT=45]-->
-    <string name="biometrics_unlock_set_unlock_pattern">Pattern \u2022 Face \u2022 Fingerprint</string>
-    <!-- Title for preference that guides the user through creating a backup unlock PIN for biometrics unlock [CHAR LIMIT=45]-->
-    <string name="biometrics_unlock_set_unlock_pin">PIN \u2022 Face \u2022 Fingerprint</string>
-    <!-- Title for preference that guides the user through creating a backup unlock password for biometrics unlock [CHAR LIMIT=45]-->
-    <string name="biometrics_unlock_set_unlock_password">Password \u2022 Face \u2022 Fingerprint</string>
     <!-- Title for preference that guides the user to skip face unlock setup [CHAR LIMIT=60]-->
-    <string name="biometrics_unlock_skip_biometrics">Continue without face or fingerprint</string>
+    <string name="biometrics_unlock_skip_biometrics">Continue without fingerprint or face</string>
 
     <!-- Summary for "Configure lockscreen" when lock screen is off [CHAR LIMIT=45] -->
     <string name="unlock_set_unlock_mode_off">None</string>
@@ -1856,7 +1852,7 @@
     <!-- Title for the fragment to show that the QR code is for sharing Wi-Fi hotspot network [CHAR LIMIT=50] -->
     <string name="wifi_dpp_share_hotspot">Share hotspot</string>
     <!-- Title for Wi-Fi DPP lockscreen title [CHAR LIMIT=50] -->
-    <string name="wifi_dpp_lockscreen_title">Verify that it\u0027s you</string>
+    <string name="wifi_dpp_lockscreen_title">Verify it\u0027s you</string>
     <!-- Hint for Wi-Fi password [CHAR LIMIT=50]  -->
     <string name="wifi_dpp_wifi_password">Wi\u2011Fi password: <xliff:g id="password" example="my password">%1$s</xliff:g></string>
     <!-- Hint for Wi-Fi hotspot password [CHAR LIMIT=50]  -->
@@ -2102,6 +2098,13 @@
     <!-- The footer message for Wi-Fi hotspot security settings [CHAR LIMIT=NONE] -->
     <string name="wifi_hotspot_security_footer">Security settings may change if you change the hotspot’s frequency</string>
 
+    <!-- Title for the instant hotspot state [CHAR LIMIT=NONE]-->
+    <string name="wifi_hotspot_instant_title">Instant hotspot</string>
+    <!-- Summary text when instant hotspot is turned on -->
+    <string name="wifi_hotspot_instant_summary_on">On</string>
+    <!-- Summary text when instant hotspot is turned off -->
+    <string name="wifi_hotspot_instant_summary_off">Off</string>
+
     <!-- Summary text when turning hotspot on -->
     <string name="wifi_tether_starting">Turning hotspot on\u2026</string>
     <!-- Summary text when turning hotspot off -->
@@ -2361,6 +2364,10 @@
     <string name="display_white_balance_title">Display white balance</string>
     <!-- Display settings screen, display white balance settings summary [CHAR LIMIT=NONE] -->
     <string name="display_white_balance_summary"></string>
+    <!-- Display settings screen, setting name to enable staying awake on fold [CHAR LIMIT=30] -->
+    <string name="stay_awake_on_fold_title">Stay unlocked on fold</string>
+    <!-- Display settings screen, setting summary to enable staying awake on fold [CHAR LIMIT=NONE] -->
+    <string name="stay_awake_on_fold_summary">Keep front display unlocked when folded until screen timeout</string>
     <!-- Display settings screen, peak refresh rate settings title [CHAR LIMIT=30] -->
     <string name="peak_refresh_rate_title">Smooth Display</string>
     <!-- Display settings screen, peak refresh rate settings summary [CHAR LIMIT=NONE] -->
@@ -2658,6 +2665,8 @@
     <string name="build_number">Build number</string>
     <!-- About phone screen, tapping this button will take user to a seperate UI to check Google Play system update [CHAR LIMIT=60] -->
     <string name="module_version">Google Play system update</string>
+    <!-- About phone screen, show a list of battery information  [CHAR LIMIT=60] -->
+    <string name="battery_info">Battery information</string>
 
     <!-- About phone screen, show when a value of some status item is unavailable. -->
     <string name="device_info_not_available">Not available</string>
@@ -2729,6 +2738,16 @@
     <string name="status_serial_number">Serial number</string>
     <!-- About phone, status item title.  How long the device has been running since its last reboot. -->
     <string name="status_up_time">Up time</string>
+
+    <!-- About phone, status item title. The battery manufacture date. [CHAR LIMIT=60]-->
+    <string name="battery_manufacture_date">Manufacture date</string>
+    <!-- About phone, status item title. Date of first use of the battery. [CHAR LIMIT=60]-->
+    <string name="battery_first_use_date">Date of first use</string>
+    <!-- About phone, status item title. Count of battery full charge/discharge cycles [CHAR LIMIT=60]-->
+    <string name="battery_cycle_count">Cycle count</string>
+    <!-- About phone, status item title. The status summary for cycle count that's not available. [CHAR LIMIT=40] -->
+    <string name="battery_cycle_count_not_available">Unavailable</string>
+
     <!-- SD card & phone storage settings summary. Displayed when the total memory usage is being calculated. Will be replaced with a number like "12.3 GB" when finished calucating. [CHAR LIMIT=30] -->
     <string name="memory_calculating_size">Calculating\u2026</string>
 
@@ -3012,8 +3031,6 @@
     <string name="reset_bluetooth_wifi_complete_toast">Bluetooth &amp; Wi\u2011Fi have been reset</string>
 
     <!-- Erase Euicc -->
-    <!-- Confirmation button of dialog to confirm resetting user's app preferences [CHAR LIMIT=NONE] -->
-    <string name="erase_euicc_data_button">Erase</string>
     <!-- Erase Euicc dialog and SD card & phone storage settings screen, title for the menu option and checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=50] -->
     <string name="reset_esim_title">Erase eSIMs</string>
     <!-- Erase Euicc dialog and SD card & phone storage settings screen, message for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=NONE] -->
@@ -3413,16 +3430,16 @@
 
     <!-- Message to be used to explain the users that they need to enter their pattern to continue a
          particular operation. [CHAR LIMIT=70]-->
-    <string name="lockpassword_confirm_your_pattern_generic">Use your device pattern to continue</string>
+    <string name="lockpassword_confirm_your_pattern_generic">Draw your pattern to continue</string>
     <!-- Message to be used to explain the users that they need to enter their PIN to continue a
          particular operation. [CHAR LIMIT=70]-->
-    <string name="lockpassword_confirm_your_pin_generic">Enter your device PIN to continue</string>
+    <string name="lockpassword_confirm_your_pin_generic">Enter your PIN to continue</string>
     <!-- Message to be used to explain the users that they need to enter their password to continue a
          particular operation. [CHAR LIMIT=70]-->
-    <string name="lockpassword_confirm_your_password_generic">Enter your device password to continue</string>
+    <string name="lockpassword_confirm_your_password_generic">Enter your password to continue</string>
     <!-- Message to be used to explain the users that they need to enter their work pattern to continue a
          particular operation. [CHAR LIMIT=70]-->
-    <string name="lockpassword_confirm_your_pattern_generic_profile">Use your work pattern to continue</string>
+    <string name="lockpassword_confirm_your_pattern_generic_profile">Draw your work pattern to continue</string>
     <!-- Message to be used to explain the users that they need to enter their work PIN to continue a
          particular operation. [CHAR LIMIT=70]-->
     <string name="lockpassword_confirm_your_pin_generic_profile">Enter your work PIN to continue</string>
@@ -3483,6 +3500,18 @@
     <!-- Checkbox label to set password as new screen lock if remote device credential validation succeeds. [CHAR LIMIT=43] -->
     <string name="lockpassword_remote_validation_set_password_as_screenlock">Also use password to unlock this device</string>
 
+    <!-- Header shown when pattern needs to be solved before the device exits repair mode. [CHAR LIMIT=40] -->
+    <string name="lockpassword_confirm_repair_mode_pattern_header">Verify pattern</string>
+    <!-- Header shown when the pin needs to be solved before the device exits repair mode. [CHAR LIMIT=40] -->
+    <string name="lockpassword_confirm_repair_mode_pin_header">Verify PIN</string>
+    <!-- Header shown when the password needs to be solved before the device exits repair mode. [CHAR LIMIT=40] -->
+    <string name="lockpassword_confirm_repair_mode_password_header">Verify password</string>
+    <!-- An explanation text that the pattern needs to be solved before the device exits repair mode. [CHAR LIMIT=100] -->
+    <string name="lockpassword_confirm_repair_mode_pattern_details">Use your device pattern to continue</string>
+    <!-- An explanation text that the PIN needs to be solved before the device exits repair mode. [CHAR LIMIT=100] -->
+    <string name="lockpassword_confirm_repair_mode_pin_details">Enter your device PIN to continue</string>
+    <!-- An explanation text that the password needs to be solved before the device exits repair mode. [CHAR LIMIT=100] -->
+    <string name="lockpassword_confirm_repair_mode_password_details">Enter your device password to continue</string>
 
     <!-- Security & location settings screen, change security method screen instruction if user
          enters incorrect PIN [CHAR LIMIT=30] -->
@@ -5511,6 +5540,8 @@
     <string name="battery_usage_less_than_percent">&lt; <xliff:g id="percentage">%1$s</xliff:g></string>
     <!-- Process Stats strings -->
     <skip />
+    <!-- Description of battery information footer text. [CHAR LIMIT=NONE] -->
+    <string name="battery_cycle_count_footer">Due to quality inspections before shipping, the cycle count may not be zero on first use</string>
 
     <!-- [CHAR LIMIT=NONE] Activity title for Process Stats summary -->
     <string name="process_stats_summary_title">Process Stats</string>
@@ -6405,7 +6436,7 @@
     <!-- Search keywords for the "Delete Guest Activity" section in Multiple Users Screen. [CHAR LIMIT=NONE] -->
     <string name="remove_guest_on_exit_keywords">delete, guest, activity, remove, data, visitor, erase</string>
     <!-- Title of preference to enable guest calling[CHAR LIMIT=40] -->
-    <string name="enable_guest_calling">Allow guest to use phone</string>
+    <string name="enable_guest_calling">Allow guest to make phone calls</string>
     <!-- Summary of preference to enable guest calling [CHAR LIMIT=NONE] -->
     <string name="enable_guest_calling_summary">Call history will be shared with guest user</string>
 
@@ -7008,6 +7039,9 @@
     <string name="keywords_app_pinning">screen pinning</string>
     <string name="keywords_profile_challenge">work challenge, work, profile</string>
     <string name="keywords_unification">work profile, managed profile, unify, unification, work, profile</string>
+    <string name="keywords_stay_awake_on_lock">
+        awake, sleep, do not lock, stay unlocked on fold, folding, closing, fold, close, screen off
+    </string>
     <string name="keywords_gesture">gestures</string>
     <string name="keywords_wallet">wallet</string>
     <string name="keywords_payment_settings">pay, tap, payments</string>
@@ -7022,6 +7056,7 @@
     <string name="keywords_sim_status_iccid_esim">network, mobile network state, service state, signal strength, mobile network type, roaming, iccid, eid</string>
     <string name="keywords_esim_eid">eid</string>
     <string name="keywords_model_and_hardware">serial number, hardware version</string>
+    <string name="keywords_battery_info">battery info, manufacture date, cycle count, first use</string>
     <string name="keywords_android_version">android security patch level, baseband version, kernel version</string>
     <!-- Search keywords for dark mode settings [CHAR LIMIT=NONE] -->
     <string name="keywords_dark_ui_mode">theme, light, dark, mode, light sensitivity, photophobia, make darker, darken, dark mode, migraine</string>
@@ -9622,6 +9657,15 @@
     <!-- Preference summary for battery usage list page[CHAR_LIMIT=50]-->
     <string name="app_battery_usage_summary">Set battery usage for apps</string>
 
+    <!-- Label of action button in battery tips card [CHAR LIMIT=NONE] -->
+    <string name="battery_tips_card_action_button" translatable="false">View Settings</string>
+
+    <!-- Label of dismiss button in battery tips card [CHAR LIMIT=NONE] -->
+    <string name="battery_tips_card_dismiss_button" translatable="false">Got it</string>
+
+    <!-- Feedback card message in battery tips card [CHAR LIMIT=NONE] -->
+    <string name="battery_tips_card_feedback_info" translatable="false">Is this message helpful?</string>
+
     <!-- Filter title for battery unrestricted[CHAR_LIMIT=50]-->
     <string name="filter_battery_unrestricted_title">Unrestricted</string>
 
@@ -9705,12 +9749,6 @@
     <!-- [CHAR_LIMIT=60] Label for special access screen -->
     <string name="special_access">Special app access</string>
 
-    <!-- Summary for special access settings [CHAR_LIMIT=NONE] -->
-    <plurals name="special_access_summary">
-        <item quantity="one">1 app can use unrestricted data</item>
-        <item quantity="other"><xliff:g id="count" example="10">%d</xliff:g> apps can use unrestricted data</item>
-    </plurals>
-
     <!-- Title for the See more preference item in Special app access settings [CHAR LIMIT=30] -->
     <string name="special_access_more">See more</string>
 
@@ -10511,7 +10549,7 @@
     <!-- Debugging developer settings: enable angle as system driver? [CHAR LIMIT=50] -->
     <string name="enable_angle_as_system_driver">Enable ANGLE</string>
     <!-- Debugging developer settings: enable angle as system driver summary [CHAR LIMIT=NONE] -->
-    <string name="enable_angle_as_system_driver_summary">Enable ANGLE as system OpenGL ES driver</string>
+    <string name="enable_angle_as_system_driver_summary">Enable ANGLE as default OpenGL ES driver. Enabling it on incompatible devices may break some applications.</string>
     <!--Dialog body text used to explain a reboot is required after changing ANGLE as system GLES driver setting-->
     <string name="reboot_dialog_enable_angle_as_system_driver">A reboot is required to change the system OpenGL ES driver</string>
 
@@ -10527,8 +10565,6 @@
     <string name="platform_compat_default_disabled_title">Default disabled changes</string>
     <!-- Title for target SDK gated app compat changes category (do not translate 'targetSdkVersion') [CHAR LIMIT=50] -->
     <string name="platform_compat_target_sdk_title">Enabled for targetSdkVersion &gt;= <xliff:g id="number" example="29">%d</xliff:g></string>
-    <!-- Title for the dialog shown when no debuggable apps are available [CHAR LIMIT=30] -->
-    <string name="platform_compat_dialog_title_no_apps">No apps available</string>
     <!-- Explanatory text shown when no debuggable apps are available [CHAR LIMIT=NONE] -->
     <string name="platform_compat_dialog_text_no_apps">App compatibility changes can only be modified for debuggable apps. Install a debuggable app and try again.</string>
 
@@ -11970,6 +12006,19 @@
     <!-- The summary of the head tracking [CHAR LIMIT=none] -->
     <string name="bluetooth_details_head_tracking_summary">Audio changes as you move your head to sound more natural</string>
 
+    <!-- The title of the bluetooth audio device type selection [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_audio_device_types_title">Audio Device Type</string>
+    <!-- The audio device type corresponding to unknown selected [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_audio_device_type_unknown">Unknown</string>
+    <!-- The audio device type corresponding to none selected [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_audio_device_type_speaker">Speaker</string>
+    <!-- The audio device type corresponding to speakers [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_audio_device_type_headphones">Headphones</string>
+    <!-- The audio device type corresponding to car kit [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_audio_device_type_carkit">Car Kit</string>
+    <!-- The audio device type corresponding to other device type [CHAR LIMIT=none] -->
+    <string name="bluetooth_details_audio_device_type_other">Other</string>
+
     <!-- Developer Settings: Title for network bandwidth ingress rate limit [CHAR LIMIT=none] -->
     <string name="ingress_rate_limit_title">Network download rate limit</string>
     <!-- Developer Settings: Summary for network bandwidth ingress rate limit [CHAR LIMIT=none] -->
@@ -11987,7 +12036,7 @@
     <!-- Developer settings: Title for force enabling Notes role. [CHAR LIMIT=50]-->
     <string name="enable_notes_role_title">Force enable Notes role</string>
     <!-- Developer settings: Summary for disabling phantom process monitoring. [CHAR LIMIT=NONE]-->
-    <string name="enable_notes_role_summary">Enable note-taking system integrations via the Notes role. If the Notes role is already enabled, does nothing.</string>
+    <string name="enable_notes_role_summary">Enable note-taking system integrations via the Notes role. If the Notes role is already enabled, does nothing. Requires reboot.</string>
 
 
     <!-- BT LE Audio Device: Media Broadcast -->
@@ -12061,6 +12110,33 @@
     other {Apps installed more than # months ago}
     }</string>
 
+    <!-- App Aspect Ratio (User Aspect Ratio Override) -->
+    <!-- [CHAR LIMIT=60] Aspect ratio title setting to choose app aspect ratio -->
+    <string name="aspect_ratio_title">Aspect ratio</string>
+    <!-- [CHAR LIMIT=NONE] Aspect ratio setting summary to choose aspect ratio for apps unoptimized for device -->
+    <string name="aspect_ratio_summary">Choose an aspect ratio to view this app if it hasn\'t been designed to fit your <xliff:g id="device_name">%1$s</xliff:g></string>
+    <!-- [CHAR LIMIT=NONE] Aspect ratio suggested apps filter label -->
+    <string name="user_aspect_ratio_suggested_apps_label">Suggested apps</string>
+    <!-- [CHAR LIMIT=NONE] Filter label for apps that have user aspect ratio override applied -->
+    <string name="user_aspect_ratio_overridden_apps_label">Apps you have overridden</string>
+    <!-- [CHAR LIMIT=NONE] App default aspect ratio entry -->
+    <string name="user_aspect_ratio_app_default">App default</string>
+    <!-- [CHAR LIMIT=NONE] Fullscreen aspect ratio entry -->
+    <string name="user_aspect_ratio_fullscreen">Full screen</string>
+    <!-- [CHAR LIMIT=NONE] Half screen aspect ratio entry -->
+    <string name="user_aspect_ratio_half_screen">Half screen</string>
+    <!-- [CHAR LIMIT=NONE] Device display size aspect ratio entry -->
+    <string name="user_aspect_ratio_device_size">Device aspect ratio</string>
+    <!-- [CHAR LIMIT=NONE] 16:9 aspect ratio entry -->
+    <string name="user_aspect_ratio_16_9">16:9</string>
+    <!-- [CHAR LIMIT=NONE] 3:2 aspect ratio entry -->
+    <string name="user_aspect_ratio_3_2">3:2</string>
+    <!-- [CHAR LIMIT=NONE] 4:3 aspect ratio entry -->
+    <string name="user_aspect_ratio_4_3">4:3</string>
+    <!-- [CHAR LIMIT=NONE] Warning description for app info aspect ratio page -->
+    <string name="app_aspect_ratio_footer">The app will restart when you change aspect ratio. You may lose unsaved changes.</string>
+
+
     <!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
     <string name="accessibility_fingerprint_label">Fingerprint sensor</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fe15226..ee78a45 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -947,4 +947,10 @@
         <item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
         <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
     </style>
+
+    <style name="ScreenLockPasswordHintTextFontStyle">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:fontFamily">google-sans-text</item>
+    </style>
+
 </resources>
diff --git a/res/xml/apps.xml b/res/xml/apps.xml
index ae51bae..651ed9b 100644
--- a/res/xml/apps.xml
+++ b/res/xml/apps.xml
@@ -80,6 +80,18 @@
         android:order="10"/>
 
     <Preference
+        android:key="aspect_ratio_apps"
+        android:title="@string/aspect_ratio_title"
+        android:summary="@string/summary_placeholder"
+        android:order="14"
+        settings:controller="com.android.settings.applications.appcompat.UserAspectRatioAppsPreferenceController"
+        android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
+        <extra android:name="classname"
+               android:value="com.android.settings.Settings$UserAspectRatioAppListActivity"/>
+        <intent android:action="android.settings.MANAGE_USER_ASPECT_RATIO_SETTINGS"/>
+    </Preference>
+
+    <Preference
         android:key="hibernated_apps"
         android:title="@string/unused_apps"
         android:summary="@string/summary_placeholder"
@@ -105,7 +117,6 @@
         android:key="special_access"
         android:fragment="com.android.settings.applications.specialaccess.SpecialAccessSettings"
         android:title="@string/special_access"
-        android:order="20"
-        settings:controller="com.android.settings.applications.SpecialAppAccessPreferenceController"/>
+        android:order="20"/>
 
 </PreferenceScreen>
diff --git a/res/xml/battery_info.xml b/res/xml/battery_info.xml
new file mode 100644
index 0000000..8e3c31f
--- /dev/null
+++ b/res/xml/battery_info.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/battery_info"
+    settings:keywords="@string/keywords_battery_info">
+
+    <Preference
+        android:key="battery_info_manufacture_date"
+        android:title="@string/battery_manufacture_date"
+        android:summary="@string/summary_placeholder"
+        settings:controller="com.android.settings.deviceinfo.batteryinfo.BatteryManufactureDatePreferenceController"
+        settings:enableCopying="true"/>
+
+    <Preference
+        android:key="battery_info_first_use_date"
+        android:title="@string/battery_first_use_date"
+        android:summary="@string/summary_placeholder"
+        settings:controller="com.android.settings.deviceinfo.batteryinfo.BatteryFirstUseDatePreferenceController"
+        settings:enableCopying="true"/>
+
+    <Preference
+        android:key="battery_info_cycle_count"
+        android:title="@string/battery_cycle_count"
+        android:summary="@string/summary_placeholder"
+        settings:controller="com.android.settings.deviceinfo.batteryinfo.BatteryCycleCountPreferenceController"
+        settings:enableCopying="true"/>
+
+    <com.android.settingslib.widget.FooterPreference
+        android:key="battery_info_footer"
+        android:title="@string/battery_cycle_count_footer"
+        android:selectable="false"
+        settings:searchable="false" />
+</PreferenceScreen>
diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml
index 35359f7..8f309a4 100644
--- a/res/xml/bluetooth_device_details_fragment.xml
+++ b/res/xml/bluetooth_device_details_fragment.xml
@@ -72,6 +72,9 @@
         android:key="device_controls_general" />
 
     <PreferenceCategory
+        android:key="bluetooth_audio_device_type_group"/>
+
+    <PreferenceCategory
         android:key="spatial_audio_group"/>
 
     <PreferenceCategory
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 68e4e78..32acac6 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -258,7 +258,7 @@
             android:key="platform_compat_dashboard"
             android:title="@string/platform_compat_dashboard_title"
             android:summary="@string/platform_compat_dashboard_summary"
-            android:fragment="com.android.settings.development.compat.PlatformCompatDashboard"
+            settings:controller="com.android.settings.spa.development.compat.PlatformCompatPreferenceController"
             />
 
         <SwitchPreference
@@ -464,6 +464,11 @@
             android:title="@string/pointer_location"
             android:summary="@string/pointer_location_summary" />
 
+        <SwitchPreference
+            android:key="show_key_presses"
+            android:title="@string/show_key_presses"
+            android:summary="@string/show_key_presses_summary" />
+
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index ad5236e..f94ba70 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -48,6 +48,13 @@
             settings:keywords="@string/keywords_ambient_display_screen"
             settings:controller="com.android.settings.security.screenlock.LockScreenPreferenceController"/>
 
+        <SwitchPreference
+            android:key="stay_awake_on_fold"
+            android:title="@string/stay_awake_on_fold_title"
+            android:summary="@string/stay_awake_on_fold_summary"
+            settings:keywords="@string/keywords_stay_awake_on_lock"
+            settings:controller="com.android.settings.display.StayAwakeOnFoldPreferenceController"/>
+
         <com.android.settingslib.RestrictedPreference
             android:key="screen_timeout"
             android:title="@string/screen_timeout"
diff --git a/res/xml/languages.xml b/res/xml/languages.xml
index 0f45540..5269d99 100644
--- a/res/xml/languages.xml
+++ b/res/xml/languages.xml
@@ -18,7 +18,7 @@
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:settings="http://schemas.android.com/apk/res-auto"
-    android:title="@string/language_settings">
+    android:title="@string/language_picker_title">
 
     <com.android.settingslib.widget.TopIntroPreference
         android:title="@string/desc_introduction_of_language_picker"
diff --git a/res/xml/modifier_keys_settings.xml b/res/xml/modifier_keys_settings.xml
index 63e7ee1..25525ae 100644
--- a/res/xml/modifier_keys_settings.xml
+++ b/res/xml/modifier_keys_settings.xml
@@ -21,25 +21,22 @@
     android:title="@string/modifier_keys_settings"
     android:key="modifier_keys_all"
     settings:controller="com.android.settings.inputmethod.ModifierKeysPreferenceController">
-    <Preference
+
+    <com.android.settingslib.widget.LayoutPreference
         android:key="modifier_keys_caps_lock"
-        android:title="@string/modifier_keys_caps_lock"
-        android:summary="@string/modifier_keys_default_summary"/>
+        android:layout="@layout/modifier_keys_custom_key" />
 
-    <Preference
+    <com.android.settingslib.widget.LayoutPreference
         android:key="modifier_keys_ctrl"
-        android:title="@string/modifier_keys_ctrl"
-        android:summary="@string/modifier_keys_default_summary"/>
+        android:layout="@layout/modifier_keys_custom_key" />
 
-    <Preference
+    <com.android.settingslib.widget.LayoutPreference
         android:key="modifier_keys_meta"
-        android:title="@string/modifier_keys_meta"
-        android:summary="@string/modifier_keys_default_summary"/>
+        android:layout="@layout/modifier_keys_custom_key" />
 
-    <Preference
+    <com.android.settingslib.widget.LayoutPreference
         android:key="modifier_keys_alt"
-        android:title="@string/modifier_keys_alt"
-        android:summary="@string/modifier_keys_default_summary"/>
+        android:layout="@layout/modifier_keys_custom_key" />
 
     <Preference
         android:key="modifier_keys_restore"
diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml
index 4cbe13f..6576742 100644
--- a/res/xml/my_device_info.xml
+++ b/res/xml/my_device_info.xml
@@ -144,6 +144,14 @@
             android:summary="@string/summary_placeholder"
             android:fragment="com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings"
             settings:controller="com.android.settings.deviceinfo.firmwareversion.FirmwareVersionPreferenceController"/>
+
+        <!-- Battery information -->
+        <Preference
+            android:key="battery_info"
+            android:order="43"
+            android:title="@string/battery_info"
+            android:fragment="com.android.settings.deviceinfo.batteryinfo.BatteryInfoFragment"
+            settings:keywords="@string/keywords_battery_info"/>
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml
index 2a1a23c..c129453 100644
--- a/res/xml/power_usage_advanced.xml
+++ b/res/xml/power_usage_advanced.xml
@@ -21,6 +21,18 @@
     android:title="@string/advanced_battery_title"
     settings:keywords="@string/keywords_battery_usage">
 
+    <PreferenceCategory
+        android:key="battery_tips_category"
+        settings:controller=
+            "com.android.settings.fuelgauge.batteryusage.BatteryTipsController"
+        settings:isPreferenceVisible="false">
+
+        <com.android.settings.fuelgauge.batteryusage.BatteryTipsCardPreference
+            android:key="battery_tips_card"
+            settings:isPreferenceVisible="false" />
+
+    </PreferenceCategory>
+
     <com.android.settings.fuelgauge.batteryusage.BatteryHistoryPreference
         android:key="battery_chart"
         settings:controller=
diff --git a/res/xml/security_settings_fingerprint_limbo.xml b/res/xml/security_settings_fingerprint_limbo.xml
new file mode 100644
index 0000000..02a3dfb
--- /dev/null
+++ b/res/xml/security_settings_fingerprint_limbo.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/security_settings_fingerprint_preference_title">
+
+    <PreferenceCategory
+        android:key="security_settings_fingerprints_enrolled"
+        settings:controller="com.android.settings.biometrics.fingerprint.FingerprintsEnrolledCategoryPreferenceController">
+    </PreferenceCategory>
+
+    <androidx.preference.Preference
+        android:icon="@drawable/ic_add_24dp"
+        android:key="key_fingerprint_add"
+        android:title="@string/fingerprint_add_title" />
+
+    <PreferenceCategory
+        android:key="security_settings_fingerprint_unlock_category"
+        android:title="@string/security_settings_fingerprint_settings_preferences_category"
+        android:visibility="gone">
+
+        <com.android.settingslib.RestrictedSwitchPreference
+            android:key="security_settings_require_screen_on_to_auth"
+            android:title="@string/security_settings_require_screen_on_to_auth_title"
+            android:summary="@string/security_settings_require_screen_on_to_auth_description"
+            settings:keywords="@string/security_settings_require_screen_on_to_auth_keywords"
+            settings:controller="com.android.settings.biometrics.fingerprint.FingerprintSettingsRequireScreenOnToAuthPreferenceController" />
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="security_settings_fingerprint_footer">
+    </PreferenceCategory>
+
+</PreferenceScreen>
+
diff --git a/res/xml/stylus_usi_details_fragment.xml b/res/xml/stylus_usi_details_fragment.xml
index 8a1d036..639c284 100644
--- a/res/xml/stylus_usi_details_fragment.xml
+++ b/res/xml/stylus_usi_details_fragment.xml
@@ -30,4 +30,7 @@
     <PreferenceCategory
         android:key="device_stylus"/>
 
+    <PreferenceCategory
+        android:key="stylus_usb_firmware"
+        settings:controller="com.android.settings.connecteddevice.stylus.StylusUsbFirmwareController"/>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/user_aspect_ratio_details.xml b/res/xml/user_aspect_ratio_details.xml
new file mode 100644
index 0000000..07c8b6f
--- /dev/null
+++ b/res/xml/user_aspect_ratio_details.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:title="@string/aspect_ratio_title">
+
+    <com.android.settingslib.widget.ActionButtonsPreference
+        android:key="header_view" />
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="app_default_pref"
+        android:title="@string/user_aspect_ratio_app_default"/>
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="fullscreen_pref"
+        android:title="@string/user_aspect_ratio_fullscreen"
+        android:icon="@drawable/ic_app_aspect_ratio_fullscreen"/>
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="half_screen_pref"
+        android:title="@string/user_aspect_ratio_half_screen"
+        android:icon="@drawable/ic_app_aspect_ratio_half_screen"/>
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="display_size_pref"
+        android:title="@string/user_aspect_ratio_device_size"
+        android:icon="@drawable/ic_app_aspect_ratio_display_size"/>
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="16_9_pref"
+        android:title="@string/user_aspect_ratio_16_9"
+        android:icon="@drawable/ic_app_aspect_ratio_16_9"/>
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="4_3_pref"
+        android:title="@string/user_aspect_ratio_4_3"
+        android:icon="@drawable/ic_app_aspect_ratio_4_3"/>
+
+    <com.android.settings.applications.appcompat.RadioWithImagePreference
+        android:key="3_2_pref"
+        android:title="@string/user_aspect_ratio_3_2"
+        android:icon="@drawable/ic_app_aspect_ratio_3_2"/>
+
+    <com.android.settingslib.widget.FooterPreference
+        android:title="@string/app_aspect_ratio_footer"
+        android:selectable="false"
+        settings:searchable="false"/>
+
+</PreferenceScreen>
diff --git a/res/layout/wifi_api_test.xml b/res/xml/wifi_api_test.xml
similarity index 100%
rename from res/layout/wifi_api_test.xml
rename to res/xml/wifi_api_test.xml
diff --git a/res/xml/wifi_tether_settings.xml b/res/xml/wifi_tether_settings.xml
index a85d9ea..b8b810f 100644
--- a/res/xml/wifi_tether_settings.xml
+++ b/res/xml/wifi_tether_settings.xml
@@ -59,4 +59,10 @@
         android:summary="@string/summary_placeholder"
         android:fragment="com.android.settings.wifi.tether.WifiHotspotSpeedSettings"
         settings:isPreferenceVisible="@bool/config_show_wifi_hotspot_speed"/>
+
+    <Preference
+        android:key="wifi_hotspot_instant"
+        android:title="@string/wifi_hotspot_instant_title"
+        android:summary="@string/summary_placeholder"
+        settings:isPreferenceVisible="false"/>
 </PreferenceScreen>
diff --git a/src/com/android/settings/RegulatoryInfoDisplayActivity.kt b/src/com/android/settings/RegulatoryInfoDisplayActivity.kt
index ffacb9c..fdf66c3 100644
--- a/src/com/android/settings/RegulatoryInfoDisplayActivity.kt
+++ b/src/com/android/settings/RegulatoryInfoDisplayActivity.kt
@@ -46,7 +46,7 @@
 
         getRegulatoryInfo()?.let {
             val view = layoutInflater.inflate(R.layout.regulatory_info, null)
-            val image = view.findViewById<ImageView>(R.id.regulatoryInfo)
+            val image = view.requireViewById<ImageView>(R.id.regulatoryInfo)
             image.setImageDrawable(it)
             builder.setView(view)
             builder.show()
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index a67aeaa..a1a7cda 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -56,6 +56,7 @@
     /** Container for {@link FaceSettings} to use with a pre-defined task affinity. */
     public static class FaceSettingsInternalActivity extends SettingsActivity { /* empty */ }
     public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }
+    public static class FingerprintSettingsActivityV2 extends SettingsActivity { /* empty */ }
     public static class CombinedBiometricSettingsActivity extends SettingsActivity { /* empty */ }
     public static class CombinedBiometricProfileSettingsActivity extends SettingsActivity { /* empty */ }
     public static class TetherSettingsActivity extends SettingsActivity {
@@ -360,6 +361,8 @@
     public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
     /** Activity to manage Cloned Apps page */
     public static class ClonedAppsListActivity extends SettingsActivity { /* empty */ }
+    /** Activity to manage Aspect Ratio app list page */
+    public static class UserAspectRatioAppListActivity extends SettingsActivity { /* empty */ }
     public static class NotificationReviewPermissionsActivity extends SettingsActivity { /* empty */ }
     public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsActivityUtil.kt b/src/com/android/settings/SettingsActivityUtil.kt
index cac341f..65d26de 100644
--- a/src/com/android/settings/SettingsActivityUtil.kt
+++ b/src/com/android/settings/SettingsActivityUtil.kt
@@ -35,6 +35,7 @@
 import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
 import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
 import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
+import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
 import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
 import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
 import com.android.settings.wifi.ChangeWifiStateDetails
@@ -62,6 +63,8 @@
             MediaManagementAppsAppListProvider.getAppInfoRoutePrefix(),
         ChangeWifiStateDetails::class.qualifiedName to
             WifiControlAppListProvider.getAppInfoRoutePrefix(),
+        NfcTagAppsSettingsProvider::class.qualifiedName to
+            NfcTagAppsSettingsProvider.getAppInfoRoutePrefix(),
     )
 
     @JvmStatic
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index f9f4cdf..dda5b24 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -16,6 +16,9 @@
 
 package com.android.settings;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PASSWORD;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
 import static android.content.Intent.EXTRA_USER;
 import static android.content.Intent.EXTRA_USER_ID;
 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
@@ -708,9 +711,13 @@
         final int userId = bundle.getInt(Intent.EXTRA_USER_ID, UserHandle.myUserId());
         if (userId == LockPatternUtils.USER_FRP) {
             return allowAnyUser ? userId : checkUserOwnsFrpCredential(context, userId);
-        } else {
-            return allowAnyUser ? userId : enforceSameOwner(context, userId);
         }
+        if (userId == LockPatternUtils.USER_REPAIR_MODE) {
+            enforceRepairModeActive(context);
+            // any users can exit repair mode
+            return userId;
+        }
+        return allowAnyUser ? userId : enforceSameOwner(context, userId);
     }
 
     /**
@@ -730,6 +737,16 @@
     }
 
     /**
+     * Throws {@link SecurityException} if repair mode is not active on the device.
+     */
+    private static void enforceRepairModeActive(Context context) {
+        if (LockPatternUtils.isRepairModeActive(context)) {
+            return;
+        }
+        throw new SecurityException("Repair mode is not active on the device.");
+    }
+
+    /**
      * Returns the given user id if it belongs to the current user.
      *
      * @throws SecurityException if the given userId does not belong to the current user group.
@@ -768,6 +785,47 @@
         return lpu.getCredentialTypeForUser(userId);
     }
 
+    /**
+     * Returns the confirmation credential string of the given user id.
+     */
+    @Nullable public static String getConfirmCredentialStringForUser(@NonNull Context context,
+             int userId, @LockPatternUtils.CredentialType int credentialType) {
+        final int effectiveUserId = UserManager.get(context).getCredentialOwnerProfile(userId);
+        final boolean isEffectiveUserManagedProfile = UserManager.get(context)
+                .isManagedProfile(effectiveUserId);
+        final DevicePolicyManager devicePolicyManager = context
+                .getSystemService(DevicePolicyManager.class);
+        switch (credentialType) {
+            case LockPatternUtils.CREDENTIAL_TYPE_PIN:
+                if (isEffectiveUserManagedProfile) {
+                    return devicePolicyManager.getResources().getString(WORK_PROFILE_CONFIRM_PIN,
+                            () -> context.getString(
+                                    R.string.lockpassword_confirm_your_pin_generic_profile));
+                }
+
+                return context.getString(R.string.lockpassword_confirm_your_pin_generic);
+            case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
+                if (isEffectiveUserManagedProfile) {
+                    return devicePolicyManager.getResources().getString(
+                            WORK_PROFILE_CONFIRM_PATTERN,
+                            () -> context.getString(
+                                    R.string.lockpassword_confirm_your_pattern_generic_profile));
+                }
+
+                return context.getString(R.string.lockpassword_confirm_your_pattern_generic);
+            case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
+                if (isEffectiveUserManagedProfile) {
+                    return devicePolicyManager.getResources().getString(
+                            WORK_PROFILE_CONFIRM_PASSWORD,
+                            () -> context.getString(
+                                    R.string.lockpassword_confirm_your_password_generic_profile));
+                }
+
+                return context.getString(R.string.lockpassword_confirm_your_password_generic);
+        }
+        return null;
+    }
+
     private static final StringBuilder sBuilder = new StringBuilder(50);
     private static final java.util.Formatter sFormatter = new java.util.Formatter(
             sBuilder, Locale.getDefault());
diff --git a/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java
index 9681a42..e82cd96 100644
--- a/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java
+++ b/src/com/android/settings/accessibility/AccessibilityQuickSettingsPrimarySwitchPreferenceController.java
@@ -66,6 +66,10 @@
     @Override
     public void onDestroy() {
         mHandler.removeCallbacksAndMessages(null);
+        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
+        if (isTooltipWindowShowing) {
+            mTooltipWindow.dismiss();
+        }
     }
 
     @Override
@@ -126,10 +130,17 @@
             return;
         }
 
-        mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
-        mTooltipWindow.setup(getTileTooltipContent(),
-                R.drawable.accessibility_auto_added_qs_tooltip_illustration);
-        mTooltipWindow.showAtTopCenter(mPreference.getSwitch());
+        // TODO (287728819): Move tooltip showing to SystemUI
+        // Since the lifecycle of controller is independent of that of the preference, doing
+        // null check on switch is a temporary solution for the case that switch view
+        // is not ready when we would like to show the tooltip.  If the switch is not ready,
+        // we give up showing the tooltip and also do not reshow it in the future.
+        if (mPreference.getSwitch() != null) {
+            mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
+            mTooltipWindow.setup(getTileTooltipContent(),
+                    R.drawable.accessibility_auto_added_qs_tooltip_illustration);
+            mTooltipWindow.showAtTopCenter(mPreference.getSwitch());
+        }
         AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext, tileComponentName);
         mNeedsQSTooltipReshow = false;
     }
diff --git a/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java b/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java
index b3d3715..f600b03 100644
--- a/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java
+++ b/src/com/android/settings/accessibility/AvailableHearingDeviceUpdater.java
@@ -16,7 +16,6 @@
 
 package com.android.settings.accessibility;
 
-import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 
 import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
@@ -37,11 +36,9 @@
 
     @Override
     public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
-        final BluetoothDevice device = cachedDevice.getDevice();
-        final boolean isConnectedHearingAidDevice = (cachedDevice.isConnectedHearingAidDevice()
-                && (device.getBondState() == BluetoothDevice.BOND_BONDED));
-
-        return isConnectedHearingAidDevice && isDeviceInCachedDevicesList(cachedDevice);
+        return cachedDevice.isHearingAidDevice()
+                && isDeviceConnected(cachedDevice)
+                && isDeviceInCachedDevicesList(cachedDevice);
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/HearingAidHelper.java b/src/com/android/settings/accessibility/HearingAidHelper.java
index 66a37f8..1b9bdc4 100644
--- a/src/com/android/settings/accessibility/HearingAidHelper.java
+++ b/src/com/android/settings/accessibility/HearingAidHelper.java
@@ -56,7 +56,8 @@
      * @return a list of hearing aids {@link BluetoothDevice} objects
      */
     public List<BluetoothDevice> getConnectedHearingAidDeviceList() {
-        if (!isHearingAidSupported()) {
+        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()
+                || !isHearingAidSupported()) {
             return new ArrayList<>();
         }
         final List<BluetoothDevice> deviceList = new ArrayList<>();
@@ -88,9 +89,6 @@
      * supported.
      */
     public boolean isHearingAidSupported() {
-        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
-            return false;
-        }
         final List<Integer> supportedList = mBluetoothAdapter.getSupportedProfiles();
         return supportedList.contains(BluetoothProfile.HEARING_AID)
                 || supportedList.contains(BluetoothProfile.HAP_CLIENT);
diff --git a/src/com/android/settings/accessibility/HearingAidUtils.java b/src/com/android/settings/accessibility/HearingAidUtils.java
index 42484f9..4315093 100644
--- a/src/com/android/settings/accessibility/HearingAidUtils.java
+++ b/src/com/android/settings/accessibility/HearingAidUtils.java
@@ -23,6 +23,7 @@
 
 import com.android.settings.bluetooth.HearingAidPairingDialogFragment;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CsipSetCoordinatorProfile;
 import com.android.settingslib.bluetooth.HearingAidInfo;
 
 /** Provides utility methods related hearing aids. */
@@ -40,6 +41,11 @@
      */
     public static void launchHearingAidPairingDialog(FragmentManager fragmentManager,
             @NonNull CachedBluetoothDevice device) {
+        // No need to show the pair another ear dialog if the device supports and enables CSIP.
+        // CSIP will pair other devices in the same set automatically.
+        if (isCsipSupportedAndEnabled(device)) {
+            return;
+        }
         if (device.isConnectedAshaHearingAidDevice()
                 && device.getDeviceMode() == HearingAidInfo.DeviceMode.MODE_BINAURAL
                 && device.getSubDevice() == null) {
@@ -56,4 +62,10 @@
         HearingAidPairingDialogFragment.newInstance(device.getAddress()).show(fragmentManager,
                 HearingAidPairingDialogFragment.TAG);
     }
+
+    private static boolean isCsipSupportedAndEnabled(@NonNull CachedBluetoothDevice device) {
+        return device.getProfiles().stream().anyMatch(
+                profile -> (profile instanceof CsipSetCoordinatorProfile)
+                        && (profile.isEnabled(device.getDevice())));
+    }
 }
diff --git a/src/com/android/settings/accessibility/HearingDevicePairingDetail.java b/src/com/android/settings/accessibility/HearingDevicePairingDetail.java
index de86dcf..117a8ed 100644
--- a/src/com/android/settings/accessibility/HearingDevicePairingDetail.java
+++ b/src/com/android/settings/accessibility/HearingDevicePairingDetail.java
@@ -28,7 +28,8 @@
 import com.android.settings.bluetooth.BluetoothDevicePairingDetailBase;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * HearingDevicePairingDetail is a page to scan hearing devices. This page shows scanning icons and
@@ -42,10 +43,16 @@
 
     public HearingDevicePairingDetail() {
         super();
-        final ScanFilter filter = new ScanFilter.Builder()
-                .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0})
-                .build();
-        setFilter(Collections.singletonList(filter));
+        final List<ScanFilter> filterList = new ArrayList<>();
+        // Filters for ASHA hearing aids
+        filterList.add(new ScanFilter.Builder().setServiceUuid(BluetoothUuid.HEARING_AID).build());
+        filterList.add(new ScanFilter.Builder()
+                .setServiceData(BluetoothUuid.HEARING_AID, new byte[0]).build());
+        // Filters for LE audio hearing aids
+        filterList.add(new ScanFilter.Builder().setServiceUuid(BluetoothUuid.HAS).build());
+        filterList.add(new ScanFilter.Builder()
+                .setServiceData(BluetoothUuid.HAS, new byte[0]).build());
+        setFilter(filterList);
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
index 4c860eb..6bd8747 100644
--- a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
+++ b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
@@ -28,7 +28,6 @@
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.widget.LabeledSeekBarPreference;
-import com.android.settings.widget.SeekBarPreference;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnCreate;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
@@ -111,6 +110,10 @@
     public void onDestroy() {
         // remove runnables in the queue.
         mHandler.removeCallbacksAndMessages(null);
+        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
+        if (isTooltipWindowShowing) {
+            mTooltipWindow.dismiss();
+        }
     }
 
     @Override
@@ -210,11 +213,19 @@
             return;
         }
 
-        mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
-        mTooltipWindow.setup(getTileTooltipContent(),
-                R.drawable.accessibility_auto_added_qs_tooltip_illustration);
-        mTooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar());
-        AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext, tileComponentName);
+        // TODO (287728819): Move tooltip showing to SystemUI
+        // Since the lifecycle of controller is independent of that of the preference, doing
+        // null check on seekbar is a temporary solution for the case that seekbar view
+        // is not ready when we would like to show the tooltip.  If the seekbar is not ready,
+        // we give up showing the tooltip and also do not reshow it in the future.
+        if (mSeekBarPreference.getSeekbar() != null) {
+            mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
+            mTooltipWindow.setup(getTileTooltipContent(),
+                    R.drawable.accessibility_auto_added_qs_tooltip_illustration);
+            mTooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar());
+        }
+        AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext,
+                tileComponentName);
         mNeedsQSTooltipReshow = false;
     }
 
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index edbd120..6a4344f 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -296,6 +296,10 @@
     public void onDestroyView() {
         super.onDestroyView();
         removeActionBarToggleSwitch();
+        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
+        if (isTooltipWindowShowing) {
+            mTooltipWindow.dismiss();
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/applications/AppDashboardFragment.java b/src/com/android/settings/applications/AppDashboardFragment.java
index 7e203b0..11f8405 100644
--- a/src/com/android/settings/applications/AppDashboardFragment.java
+++ b/src/com/android/settings/applications/AppDashboardFragment.java
@@ -66,7 +66,6 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle());
         mAppsPreferenceController = use(AppsPreferenceController.class);
         mAppsPreferenceController.setFragment(this /* fragment */);
         getSettingsLifecycle().addObserver(mAppsPreferenceController);
diff --git a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java
deleted file mode 100644
index 42f5930..0000000
--- a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2017 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.applications;
-
-import android.app.Application;
-import android.content.Context;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.datausage.AppStateDataUsageBridge;
-import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
-import com.android.settings.datausage.DataSaverBackend;
-import com.android.settingslib.applications.ApplicationsState;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnDestroy;
-import com.android.settingslib.core.lifecycle.events.OnStart;
-import com.android.settingslib.core.lifecycle.events.OnStop;
-
-import java.util.ArrayList;
-
-public class SpecialAppAccessPreferenceController extends BasePreferenceController implements
-        AppStateBaseBridge.Callback, ApplicationsState.Callbacks, LifecycleObserver, OnStart,
-        OnStop, OnDestroy {
-
-    @VisibleForTesting
-    ApplicationsState.Session mSession;
-
-    private final ApplicationsState mApplicationsState;
-    private final AppStateDataUsageBridge mDataUsageBridge;
-    private final DataSaverBackend mDataSaverBackend;
-
-    private Preference mPreference;
-    private boolean mExtraLoaded;
-
-
-    public SpecialAppAccessPreferenceController(Context context, String key) {
-        super(context, key);
-        mApplicationsState = ApplicationsState.getInstance(
-                (Application) context.getApplicationContext());
-        mDataSaverBackend = new DataSaverBackend(context);
-        mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
-    }
-
-    public void setSession(Lifecycle lifecycle) {
-        mSession = mApplicationsState.newSession(this, lifecycle);
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        return AVAILABLE;
-    }
-
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        mPreference = screen.findPreference(getPreferenceKey());
-    }
-
-    @Override
-    public void onStart() {
-        mDataUsageBridge.resume(true /* forceLoadAllApps */);
-    }
-
-    @Override
-    public void onStop() {
-        mDataUsageBridge.pause();
-    }
-
-    @Override
-    public void onDestroy() {
-        mDataUsageBridge.release();
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        updateSummary();
-    }
-
-    @Override
-    public void onExtraInfoUpdated() {
-        mExtraLoaded = true;
-        updateSummary();
-    }
-
-    private void updateSummary() {
-        if (!mExtraLoaded || mPreference == null) {
-            return;
-        }
-
-        final ArrayList<ApplicationsState.AppEntry> allApps = mSession.getAllApps();
-        int count = 0;
-        for (ApplicationsState.AppEntry entry : allApps) {
-            if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) {
-                continue;
-            }
-            if (entry.extraInfo instanceof DataUsageState
-                    && ((DataUsageState) entry.extraInfo).isDataSaverAllowlisted) {
-                count++;
-            }
-        }
-        mPreference.setSummary(mContext.getResources().getQuantityString(
-                R.plurals.special_access_summary, count, count));
-    }
-
-    @Override
-    public void onRunningStateChanged(boolean running) {
-    }
-
-    @Override
-    public void onPackageListChanged() {
-    }
-
-    @Override
-    public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
-    }
-
-    @Override
-    public void onPackageIconChanged() {
-    }
-
-    @Override
-    public void onPackageSizeChanged(String packageName) {
-    }
-
-    @Override
-    public void onAllSizesComputed() {
-    }
-
-    @Override
-    public void onLauncherInfoChanged() {
-        // when the value of the AppEntry.hasLauncherEntry was changed.
-        updateSummary();
-    }
-
-    @Override
-    public void onLoadEntriesCompleted() {
-    }
-}
diff --git a/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java b/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java
new file mode 100644
index 0000000..77cd86c
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/RadioWithImagePreference.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appcompat;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+/**
+ * Radio button preference with image at the bottom.
+ *
+ * <p>Layout should stay the same as
+ * {@link com.android.settingslib.widget.SelectorWithWidgetPreference} for consistency.
+ */
+public class RadioWithImagePreference extends CheckBoxPreference {
+
+    /**
+     * Interface definition for a callback to be invoked when the preference is clicked.
+     */
+    public interface OnClickListener {
+        /**
+         * Called when a preference has been clicked.
+         *
+         * @param emiter The clicked preference
+         */
+        void onRadioButtonClicked(RadioWithImagePreference emiter);
+    }
+
+    private OnClickListener mListener = null;
+
+    /**
+     * Performs inflation from XML and apply a class-specific base style.
+     *
+     * @param context  The {@link Context} this is associated with, through which it can
+     *                 access the current theme, resources, {@link SharedPreferences}, etc.
+     * @param attrs    The attributes of the XML tag that is inflating the preference
+     * @param defStyle An attribute in the current theme that contains a reference to a style
+     *                 resource that supplies default values for the view. Can be 0 to not
+     *                 look for defaults.
+     */
+    public RadioWithImagePreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    /**
+     * Performs inflation from XML and apply a class-specific base style.
+     *
+     * @param context The {@link Context} this is associated with, through which it can
+     *                access the current theme, resources, {@link SharedPreferences}, etc.
+     * @param attrs   The attributes of the XML tag that is inflating the preference
+     */
+    public RadioWithImagePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    /**
+     * Constructor to create a preference.
+     *
+     * @param context The Context this is associated with.
+     */
+    public RadioWithImagePreference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Sets the callback to be invoked when this preference is clicked by the user.
+     *
+     * @param listener The callback to be invoked
+     */
+    public void setOnClickListener(OnClickListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Processes a click on the preference.
+     */
+    @Override
+    public void onClick() {
+        if (mListener != null) {
+            mListener.onRadioButtonClicked(this);
+        }
+    }
+
+    /**
+     * Binds the created View to the data for this preference.
+     *
+     * <p>This is a good place to grab references to custom Views in the layout and set
+     * properties on them.
+     *
+     * <p>Make sure to call through to the superclass's implementation.
+     *
+     * @param holder The ViewHolder that provides references to the views to fill in. These views
+     *               will be recycled, so you should not hold a reference to them after this method
+     *               returns.
+     */
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        View summaryContainer = holder.findViewById(R.id.summary_container);
+        if (summaryContainer != null) {
+            summaryContainer.setVisibility(
+                    TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void init() {
+        setWidgetLayoutResource(com.android.settingslib.R.layout.preference_widget_radiobutton);
+        setLayoutResource(R.layout.radio_with_image_preference);
+        setIconSpaceReserved(false);
+    }
+}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java b/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java
new file mode 100644
index 0000000..ff68fb0
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioAppsPreferenceController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appcompat;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Preference controller for
+ * {@link com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider}
+ */
+public class UserAspectRatioAppsPreferenceController extends BasePreferenceController {
+
+    public UserAspectRatioAppsPreferenceController(@NonNull Context context,
+            @NonNull String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return UserAspectRatioManager.isFeatureEnabled(mContext)
+                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mContext.getResources().getString(R.string.aspect_ratio_summary, Build.MODEL);
+    }
+}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
new file mode 100644
index 0000000..fd831cd
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appcompat;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.applications.AppInfoWithHeader;
+import com.android.settingslib.widget.ActionButtonsPreference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * App specific activity to show aspect ratio overrides
+ */
+public class UserAspectRatioDetails extends AppInfoWithHeader implements
+        RadioWithImagePreference.OnClickListener {
+    private static final String TAG = UserAspectRatioDetails.class.getSimpleName();
+
+    private static final String KEY_HEADER_BUTTONS = "header_view";
+    private static final String KEY_PREF_FULLSCREEN = "fullscreen_pref";
+    private static final String KEY_PREF_HALF_SCREEN = "half_screen_pref";
+    private static final String KEY_PREF_DISPLAY_SIZE = "display_size_pref";
+    private static final String KEY_PREF_16_9 = "16_9_pref";
+    private static final String KEY_PREF_4_3 = "4_3_pref";
+    @VisibleForTesting
+    static final String KEY_PREF_DEFAULT = "app_default_pref";
+    @VisibleForTesting
+    static final String KEY_PREF_3_2 = "3_2_pref";
+
+    private final List<RadioWithImagePreference> mAspectRatioPreferences = new ArrayList<>();
+
+    @NonNull private UserAspectRatioManager mUserAspectRatioManager;
+    @NonNull private String mSelectedKey = KEY_PREF_DEFAULT;
+
+    @Override
+    public void onCreate(@NonNull Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mUserAspectRatioManager = new UserAspectRatioManager(getContext());
+        initPreferences();
+        try {
+            final int userAspectRatio = mUserAspectRatioManager
+                    .getUserMinAspectRatioValue(mPackageName, mUserId);
+            mSelectedKey = getSelectedKey(userAspectRatio);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to get user min aspect ratio");
+        }
+        refreshUi();
+    }
+
+    @Override
+    public void onRadioButtonClicked(@NonNull RadioWithImagePreference selected) {
+        final String selectedKey = selected.getKey();
+        if (mSelectedKey.equals(selectedKey)) {
+            return;
+        }
+        final int userAspectRatio = getSelectedUserMinAspectRatio(selectedKey);
+        try {
+            getAspectRatioManager().setUserMinAspectRatio(mPackageName, mUserId, userAspectRatio);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to set user min aspect ratio");
+            return;
+        }
+        // Only update to selected aspect ratio if nothing goes wrong
+        mSelectedKey = selectedKey;
+        updateAllPreferences(mSelectedKey);
+        Log.d(TAG, "Killing application process " + mPackageName);
+        try {
+            final IActivityManager am = ActivityManager.getService();
+            am.stopAppForUser(mPackageName, mUserId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to stop application " + mPackageName);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO(b/292566895): add metrics for logging
+        return 0;
+    }
+
+    @Override
+    protected boolean refreshUi() {
+        if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
+            return false;
+        }
+        updateAllPreferences(mSelectedKey);
+        return true;
+    }
+
+    @Override
+    protected AlertDialog createDialog(int id, int errorCode) {
+        return null;
+    }
+
+    private void launchApplication() {
+        Intent launchIntent = mPm.getLaunchIntentForPackage(mPackageName)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
+        if (launchIntent != null) {
+            getContext().startActivityAsUser(launchIntent, new UserHandle(mUserId));
+        }
+    }
+
+    @PackageManager.UserMinAspectRatio
+    private int getSelectedUserMinAspectRatio(@NonNull String selectedKey) {
+        switch (selectedKey) {
+            case KEY_PREF_FULLSCREEN:
+                return USER_MIN_ASPECT_RATIO_FULLSCREEN;
+            case KEY_PREF_HALF_SCREEN:
+                return USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+            case KEY_PREF_DISPLAY_SIZE:
+                return USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+            case KEY_PREF_3_2:
+                return USER_MIN_ASPECT_RATIO_3_2;
+            case KEY_PREF_4_3:
+                return USER_MIN_ASPECT_RATIO_4_3;
+            case KEY_PREF_16_9:
+                return USER_MIN_ASPECT_RATIO_16_9;
+            default:
+                return USER_MIN_ASPECT_RATIO_UNSET;
+        }
+    }
+
+    @NonNull
+    private String getSelectedKey(@PackageManager.UserMinAspectRatio int userMinAspectRatio) {
+        switch (userMinAspectRatio) {
+            case USER_MIN_ASPECT_RATIO_FULLSCREEN:
+                return KEY_PREF_FULLSCREEN;
+            case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+                return KEY_PREF_HALF_SCREEN;
+            case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+                return KEY_PREF_DISPLAY_SIZE;
+            case USER_MIN_ASPECT_RATIO_3_2:
+                return KEY_PREF_3_2;
+            case USER_MIN_ASPECT_RATIO_4_3:
+                return KEY_PREF_4_3;
+            case USER_MIN_ASPECT_RATIO_16_9:
+                return KEY_PREF_16_9;
+            default:
+                return KEY_PREF_DEFAULT;
+        }
+    }
+
+    private void initPreferences() {
+        addPreferencesFromResource(R.xml.user_aspect_ratio_details);
+
+        ((ActionButtonsPreference) findPreference(KEY_HEADER_BUTTONS))
+                .setButton1Text(R.string.launch_instant_app)
+                .setButton1Icon(R.drawable.ic_settings_open)
+                .setButton1OnClickListener(v -> launchApplication());
+
+        addPreference(KEY_PREF_DEFAULT, USER_MIN_ASPECT_RATIO_UNSET);
+        addPreference(KEY_PREF_FULLSCREEN, USER_MIN_ASPECT_RATIO_FULLSCREEN);
+        addPreference(KEY_PREF_DISPLAY_SIZE, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+        addPreference(KEY_PREF_HALF_SCREEN, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+        addPreference(KEY_PREF_16_9, USER_MIN_ASPECT_RATIO_16_9);
+        addPreference(KEY_PREF_4_3, USER_MIN_ASPECT_RATIO_4_3);
+        addPreference(KEY_PREF_3_2, USER_MIN_ASPECT_RATIO_3_2);
+    }
+
+    private void addPreference(@NonNull String key,
+            @PackageManager.UserMinAspectRatio int aspectRatio) {
+        final RadioWithImagePreference pref = findPreference(key);
+        if (pref == null) {
+            return;
+        }
+        if (!mUserAspectRatioManager.hasAspectRatioOption(aspectRatio, mPackageName)) {
+            pref.setVisible(false);
+            return;
+        }
+        pref.setTitle(mUserAspectRatioManager.getUserMinAspectRatioEntry(aspectRatio,
+                mPackageName));
+        pref.setOnClickListener(this);
+        mAspectRatioPreferences.add(pref);
+    }
+
+    private void updateAllPreferences(@NonNull String selectedKey) {
+        for (RadioWithImagePreference pref : mAspectRatioPreferences) {
+            pref.setChecked(selectedKey.equals(pref.getKey()));
+        }
+    }
+
+    @VisibleForTesting
+    UserAspectRatioManager getAspectRatioManager() {
+        return mUserAspectRatioManager;
+    }
+}
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
new file mode 100644
index 0000000..7c16ed6
--- /dev/null
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioManager.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appcompat;
+
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
+
+import static java.lang.Boolean.FALSE;
+
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class for handling app aspect ratio override
+ * {@link PackageManager.UserMinAspectRatio} set by user
+ */
+public class UserAspectRatioManager {
+    private static final Intent LAUNCHER_ENTRY_INTENT =
+            new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
+
+    // TODO(b/288142656): Enable user aspect ratio settings by default
+    private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS = false;
+    @VisibleForTesting
+    static final String KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS =
+            "enable_app_compat_user_aspect_ratio_settings";
+    static final String KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN =
+            "enable_app_compat_user_aspect_ratio_fullscreen";
+    private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true;
+
+    private final Context mContext;
+    private final IPackageManager mIPm;
+    /** Apps that have launcher entry defined in manifest */
+    private final List<ResolveInfo> mInfoHasLauncherEntryList;
+    private final Map<Integer, String> mUserAspectRatioMap;
+
+    public UserAspectRatioManager(@NonNull Context context) {
+        mContext = context;
+        mIPm = AppGlobals.getPackageManager();
+        mInfoHasLauncherEntryList = mContext.getPackageManager().queryIntentActivities(
+                UserAspectRatioManager.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA);
+        mUserAspectRatioMap = getUserMinAspectRatioMapping();
+    }
+
+    /**
+     * Whether user aspect ratio settings is enabled for device.
+     */
+    public static boolean isFeatureEnabled(Context context) {
+        final boolean isBuildTimeFlagEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled);
+        return getValueFromDeviceConfig(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+                DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS) && isBuildTimeFlagEnabled;
+    }
+
+    /**
+     * @return user-specific {@link PackageManager.UserMinAspectRatio} override for an app
+     */
+    @PackageManager.UserMinAspectRatio
+    public int getUserMinAspectRatioValue(@NonNull String packageName, int uid)
+            throws RemoteException {
+        final int aspectRatio = mIPm.getUserMinAspectRatio(packageName, uid);
+        return hasAspectRatioOption(aspectRatio, packageName)
+                ? aspectRatio : PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+    }
+
+    /**
+     * @return corresponding string for {@link PackageManager.UserMinAspectRatio} value
+     */
+    @NonNull
+    public String getUserMinAspectRatioEntry(@PackageManager.UserMinAspectRatio int aspectRatio,
+            String packageName) {
+        if (!hasAspectRatioOption(aspectRatio, packageName))  {
+            return mUserAspectRatioMap.get(PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        }
+        return mUserAspectRatioMap.get(aspectRatio);
+    }
+
+    /**
+     * @return corresponding aspect ratio string for package name and user
+     */
+    @NonNull
+    public String getUserMinAspectRatioEntry(@NonNull String packageName, int uid)
+            throws RemoteException {
+        final int aspectRatio = getUserMinAspectRatioValue(packageName, uid);
+        return getUserMinAspectRatioEntry(aspectRatio, packageName);
+    }
+
+    /**
+     * Whether user aspect ratio option is specified in
+     * {@link R.array.config_userAspectRatioOverrideValues}
+     * and is enabled by device config
+     */
+    public boolean hasAspectRatioOption(@PackageManager.UserMinAspectRatio int option,
+            String packageName) {
+        if (option == PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN
+                && !isFullscreenOptionEnabled(packageName)) {
+            return false;
+        }
+        return mUserAspectRatioMap.containsKey(option);
+    }
+
+    /**
+     * Sets user-specified {@link PackageManager.UserMinAspectRatio} override for an app
+     */
+    public void setUserMinAspectRatio(@NonNull String packageName, int uid,
+            @PackageManager.UserMinAspectRatio int aspectRatio) throws RemoteException {
+        mIPm.setUserMinAspectRatio(packageName, uid, aspectRatio);
+    }
+
+    /**
+     * Whether an app's aspect ratio can be overridden by user. Only apps with launcher entry
+     * will be overridable.
+     */
+    public boolean canDisplayAspectRatioUi(@NonNull ApplicationInfo app) {
+        Boolean appAllowsUserAspectRatioOverride = readComponentProperty(
+                mContext.getPackageManager(), app.packageName,
+                PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+        boolean hasLauncherEntry = mInfoHasLauncherEntryList.stream()
+                .anyMatch(info -> info.activityInfo.packageName.equals(app.packageName));
+        return !FALSE.equals(appAllowsUserAspectRatioOverride) && hasLauncherEntry;
+    }
+
+    /**
+     * Whether fullscreen option in per-app user aspect ratio settings is enabled
+     */
+    @VisibleForTesting
+    boolean isFullscreenOptionEnabled(String packageName) {
+        Boolean appAllowsFullscreenOption = readComponentProperty(mContext.getPackageManager(),
+                packageName, PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+        final boolean isBuildTimeFlagEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_appCompatUserAppAspectRatioFullscreenIsEnabled);
+        return !FALSE.equals(appAllowsFullscreenOption) && isBuildTimeFlagEnabled
+                && getValueFromDeviceConfig(KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
+                    DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN);
+    }
+
+    private static boolean getValueFromDeviceConfig(String name, boolean defaultValue) {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, name, defaultValue);
+    }
+
+    @NonNull
+    private Map<Integer, String> getUserMinAspectRatioMapping() {
+        final String[] userMinAspectRatioStrings = mContext.getResources().getStringArray(
+                R.array.config_userAspectRatioOverrideEntries);
+        final int[] userMinAspectRatioValues = mContext.getResources().getIntArray(
+                R.array.config_userAspectRatioOverrideValues);
+        if (userMinAspectRatioStrings.length != userMinAspectRatioValues.length) {
+            throw new RuntimeException(
+                    "config_userAspectRatioOverride options cannot be different length");
+        }
+
+        final Map<Integer, String> userMinAspectRatioMap = new ArrayMap<>();
+        for (int i = 0; i < userMinAspectRatioValues.length; i++) {
+            final int aspectRatioVal = userMinAspectRatioValues[i];
+            final String aspectRatioString = getAspectRatioStringOrDefault(
+                    userMinAspectRatioStrings[i], aspectRatioVal);
+            switch (aspectRatioVal) {
+                // Only map known values of UserMinAspectRatio and ignore unknown entries
+                case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
+                case PackageManager.USER_MIN_ASPECT_RATIO_UNSET:
+                case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+                case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+                case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
+                case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
+                case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
+                    userMinAspectRatioMap.put(aspectRatioVal, aspectRatioString);
+            }
+        }
+        if (!userMinAspectRatioMap.containsKey(PackageManager.USER_MIN_ASPECT_RATIO_UNSET)) {
+            throw new RuntimeException("config_userAspectRatioOverrideValues options must have"
+                    + " USER_MIN_ASPECT_RATIO_UNSET value");
+        }
+        return userMinAspectRatioMap;
+    }
+
+    @NonNull
+    private String getAspectRatioStringOrDefault(@Nullable String aspectRatioString,
+            @PackageManager.UserMinAspectRatio int aspectRatioVal) {
+        if (aspectRatioString != null) {
+            return aspectRatioString;
+        }
+        // Options are customized per device and if strings are set to @null, use default
+        switch (aspectRatioVal) {
+            case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
+                return mContext.getString(R.string.user_aspect_ratio_fullscreen);
+            case PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+                return mContext.getString(R.string.user_aspect_ratio_half_screen);
+            case PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+                return mContext.getString(R.string.user_aspect_ratio_device_size);
+            case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
+                return mContext.getString(R.string.user_aspect_ratio_4_3);
+            case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
+                return mContext.getString(R.string.user_aspect_ratio_16_9);
+            case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
+                return mContext.getString(R.string.user_aspect_ratio_3_2);
+            default:
+                return mContext.getString(R.string.user_aspect_ratio_app_default);
+        }
+    }
+
+    @Nullable
+    private static Boolean readComponentProperty(PackageManager pm, String packageName,
+            String propertyName) {
+        try {
+            return pm.getProperty(propertyName, packageName).getBoolean();
+        } catch (PackageManager.NameNotFoundException e) {
+            // No such property name
+        }
+        return null;
+    }
+
+    @VisibleForTesting
+    void addInfoHasLauncherEntry(@NonNull ResolveInfo infoHasLauncherEntry) {
+        mInfoHasLauncherEntryList.add(infoHasLauncherEntry);
+    }
+}
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 548ca55..d734a27 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -269,6 +269,7 @@
     public static final int LIST_TYPE_CLONED_APPS = 17;
     public static final int LIST_TYPE_NFC_TAG_APPS = 18;
     public static final int LIST_TYPE_TURN_SCREEN_ON = 19;
+    public static final int LIST_TYPE_USER_ASPECT_RATIO_APPS = 20;
 
     // List types that should show instant apps.
     public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
index 78a4a6b..8313686 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
+++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
@@ -20,6 +20,7 @@
 import android.util.FeatureFlagUtils
 import com.android.settings.Settings.AlarmsAndRemindersActivity
 import com.android.settings.Settings.AppBatteryUsageActivity
+import com.android.settings.Settings.UserAspectRatioAppListActivity
 import com.android.settings.Settings.ChangeNfcTagAppsActivity
 import com.android.settings.Settings.ChangeWifiStateActivity
 import com.android.settings.Settings.ClonedAppsListActivity
@@ -40,6 +41,7 @@
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_MANAGE_EXTERNAL_STORAGE
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_ALARMS_AND_REMINDERS
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_APPS_LOCALE
+import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USER_ASPECT_RATIO_APPS
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_CLONED_APPS
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_GAMES
@@ -57,12 +59,14 @@
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WIFI_ACCESS
 import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS
 import com.android.settings.spa.app.AllAppListPageProvider
+import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
 import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
 import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
 import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
 import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
 import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
 import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
+import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
 import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
 import com.android.settings.spa.notification.AppListNotificationsPageProvider
 import com.android.settings.spa.system.AppLanguagesPageProvider
@@ -91,6 +95,7 @@
         ClonedAppsListActivity::class to LIST_TYPE_CLONED_APPS,
         ChangeNfcTagAppsActivity::class to LIST_TYPE_NFC_TAG_APPS,
         TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON,
+        UserAspectRatioAppListActivity::class to LIST_TYPE_USER_ASPECT_RATIO_APPS,
     )
 
     @JvmField
@@ -112,6 +117,8 @@
             LIST_TYPE_NOTIFICATION -> AppListNotificationsPageProvider.name
             LIST_TYPE_APPS_LOCALE -> AppLanguagesPageProvider.name
             LIST_TYPE_MAIN -> AllAppListPageProvider.name
+            LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
+            LIST_TYPE_USER_ASPECT_RATIO_APPS -> UserAspectRatioAppsPageProvider.name
             else -> null
         }
     }
diff --git a/src/com/android/settings/applications/specialaccess/DataSaverController.java b/src/com/android/settings/applications/specialaccess/DataSaverController.java
deleted file mode 100644
index d1fd202..0000000
--- a/src/com/android/settings/applications/specialaccess/DataSaverController.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 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.applications.specialaccess;
-
-import android.content.Context;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-
-public class DataSaverController extends BasePreferenceController {
-
-    public DataSaverController(Context context, String key) {
-        super(context, key);
-    }
-
-    @AvailabilityStatus
-    public int getAvailabilityStatus() {
-        return mContext.getResources().getBoolean(R.bool.config_show_data_saver)
-                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
-    }
-}
diff --git a/src/com/android/settings/applications/specialaccess/DataSaverController.kt b/src/com/android/settings/applications/specialaccess/DataSaverController.kt
new file mode 100644
index 0000000..baed0aa
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/DataSaverController.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 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.applications.specialaccess
+
+import android.content.Context
+import android.net.NetworkPolicyManager
+import android.os.UserHandle
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.spa.framework.util.formatString
+import com.android.settingslib.spaprivileged.model.app.AppListRepository
+import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl
+import com.google.common.annotations.VisibleForTesting
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class DataSaverController(context: Context, key: String) : BasePreferenceController(context, key) {
+
+    private lateinit var preference: Preference
+
+    @AvailabilityStatus
+    override fun getAvailabilityStatus(): Int = when {
+        mContext.resources.getBoolean(R.bool.config_show_data_saver) -> AVAILABLE
+        else -> UNSUPPORTED_ON_DEVICE
+    }
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        preference = screen.findPreference(preferenceKey)!!
+    }
+
+    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        viewLifecycleOwner.lifecycleScope.launch {
+            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                preference.summary = getUnrestrictedSummary(mContext)
+            }
+        }
+    }
+
+    companion object {
+        @VisibleForTesting
+        suspend fun getUnrestrictedSummary(
+            context: Context,
+            appListRepository: AppListRepository =
+                AppListRepositoryImpl(context.applicationContext),
+        ) = context.formatString(
+            R.string.data_saver_unrestricted_summary,
+            "count" to getAllowCount(context.applicationContext, appListRepository),
+        )
+
+        private suspend fun getAllowCount(context: Context, appListRepository: AppListRepository) =
+            withContext(Dispatchers.IO) {
+                coroutineScope {
+                    val appsDeferred = async {
+                        appListRepository.loadAndFilterApps(
+                            userId = UserHandle.myUserId(),
+                            isSystemApp = false,
+                        )
+                    }
+                    val uidsAllowed = NetworkPolicyManager.from(context)
+                        .getUidsWithPolicy(NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND)
+                    appsDeferred.await().count { app -> app.uid in uidsAllowed }
+                }
+            }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
index 2a350f4..46f534d 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java
@@ -236,6 +236,9 @@
     protected void onResume() {
         super.onResume();
 
+        //reset mNextClick to make sure introduction page would be closed correctly
+        mNextClicked = false;
+
         final int errorMsg = checkMaxEnrolled();
         if (errorMsg == 0) {
             mErrorText.setText(null);
diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java
index 3356dfa..4e1a2f3 100644
--- a/src/com/android/settings/biometrics/BiometricUtils.java
+++ b/src/com/android/settings/biometrics/BiometricUtils.java
@@ -527,17 +527,18 @@
         // Assume the flow is "Screen Lock" + "Face" + "Fingerprint"
         ssb.append(bidi.unicodeWrap(screenLock));
 
+        if (hasFingerprint) {
+            ssb.append(bidi.unicodeWrap(SEPARATOR));
+            ssb.append(bidi.unicodeWrap(
+                    capitalize(context.getString(R.string.security_settings_fingerprint))));
+        }
+
         if (isFaceSupported) {
             ssb.append(bidi.unicodeWrap(SEPARATOR));
             ssb.append(bidi.unicodeWrap(
                     capitalize(context.getString(R.string.keywords_face_settings))));
         }
 
-        if (hasFingerprint) {
-            ssb.append(bidi.unicodeWrap(SEPARATOR));
-            ssb.append(bidi.unicodeWrap(
-                    capitalize(context.getString(R.string.security_settings_fingerprint))));
-        }
         return ssb.toString();
     }
 
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 487e254..69ae9a7 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -21,6 +21,7 @@
 
 import static com.android.settings.password.ChooseLockPattern.RESULT_FINISHED;
 
+import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.biometrics.SensorProperties;
@@ -179,6 +180,12 @@
             }
 
             mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
+                final Activity activity = getActivity();
+                if (activity == null || activity.isFinishing()) {
+                    Log.e(getLogTag(), "Stop during generating face unlock challenge"
+                            + " because activity is null or finishing");
+                    return;
+                }
                 try {
                     final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId,
                             challenge);
@@ -215,6 +222,12 @@
             }
 
             mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
+                final Activity activity = getActivity();
+                if (activity == null || activity.isFinishing()) {
+                    Log.e(getLogTag(), "Stop during generating fingerprint challenge"
+                            + " because activity is null or finishing");
+                    return;
+                }
                 try {
                     final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId,
                             challenge);
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index bff998a..bea0c33 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -120,6 +120,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         mFaceManager = getFaceManager();
 
+        super.onCreate(savedInstanceState);
+
         if (savedInstanceState == null
                 && !WizardManagerHelper.isAnySetupWizard(getIntent())
                 && !getIntent().getBooleanExtra(EXTRA_FROM_SETTINGS_SUMMARY, false)
@@ -130,8 +132,6 @@
             finish();
         }
 
-        super.onCreate(savedInstanceState);
-
         // Wait super::onCreated() then return because SuperNotCalledExceptio will be thrown
         // if we don't wait for it.
         if (isFinishing()) {
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index 1e74ad7..4b2e336 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -30,6 +30,7 @@
 import android.widget.Button;
 import android.widget.Toast;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
 import com.android.settings.R;
@@ -57,10 +58,18 @@
     static final String KEY = "security_settings_face_delete_faces_container";
 
     public static class ConfirmRemoveDialog extends InstrumentedDialogFragment {
-
-        private boolean mIsConvenience;
+        private static final String KEY_IS_CONVENIENCE = "is_convenience";
         private DialogInterface.OnClickListener mOnClickListener;
 
+        /** Returns the new instance of the class */
+        public static ConfirmRemoveDialog newInstance(boolean isConvenience) {
+            final ConfirmRemoveDialog dialog = new ConfirmRemoveDialog();
+            final Bundle args = new Bundle();
+            args.putBoolean(KEY_IS_CONVENIENCE, isConvenience);
+            dialog.setArguments(args);
+            return dialog;
+        }
+
         @Override
         public int getMetricsCategory() {
             return SettingsEnums.DIALOG_FACE_REMOVE;
@@ -68,6 +77,8 @@
 
         @Override
         public Dialog onCreateDialog(Bundle savedInstanceState) {
+            boolean isConvenience = getArguments().getBoolean(KEY_IS_CONVENIENCE);
+
             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
 
             final PackageManager pm = getContext().getPackageManager();
@@ -75,11 +86,11 @@
             final int dialogMessageRes;
 
             if (hasFingerprint) {
-                dialogMessageRes = mIsConvenience
+                dialogMessageRes = isConvenience
                         ? R.string.security_settings_face_remove_dialog_details_fingerprint_conv
                         : R.string.security_settings_face_remove_dialog_details_fingerprint;
             } else {
-                dialogMessageRes = mIsConvenience
+                dialogMessageRes = isConvenience
                         ? R.string.security_settings_face_settings_remove_dialog_details_convenience
                         : R.string.security_settings_face_settings_remove_dialog_details;
             }
@@ -93,10 +104,6 @@
             return dialog;
         }
 
-        public void setIsConvenience(boolean isConvenience) {
-            mIsConvenience = isConvenience;
-        }
-
         public void setOnClickListener(DialogInterface.OnClickListener listener) {
             mOnClickListener = listener;
         }
@@ -111,7 +118,8 @@
     private Listener mListener;
     private SettingsActivity mActivity;
     private int mUserId;
-    private boolean mRemoving;
+    @VisibleForTesting
+    boolean mRemoving;
 
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final Context mContext;
@@ -142,7 +150,7 @@
         }
     };
 
-    private final DialogInterface.OnClickListener mOnClickListener
+    private final DialogInterface.OnClickListener mOnConfirmDialogClickListener
             = new DialogInterface.OnClickListener() {
         @Override
         public void onClick(DialogInterface dialog, int which) {
@@ -196,6 +204,16 @@
 
         mButton.setOnClickListener(this);
 
+        // If there is already a ConfirmRemoveDialog showing, reset the listener since the
+        // controller has been recreated.
+        ConfirmRemoveDialog removeDialog =
+                (ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
+                        .findFragmentByTag(ConfirmRemoveDialog.class.getName());
+        if (removeDialog != null) {
+            mRemoving = true;
+            removeDialog.setOnClickListener(mOnConfirmDialogClickListener);
+        }
+
         if (!FaceSettings.isFaceHardwareDetected(mContext)) {
             mButton.setEnabled(false);
         } else {
@@ -218,10 +236,11 @@
         if (v == mButton) {
             mMetricsFeatureProvider.logClickedPreference(mPreference, getMetricsCategory());
             mRemoving = true;
-            ConfirmRemoveDialog dialog = new ConfirmRemoveDialog();
-            dialog.setOnClickListener(mOnClickListener);
-            dialog.setIsConvenience(BiometricUtils.isConvenience(mFaceManager));
-            dialog.show(mActivity.getSupportFragmentManager(), ConfirmRemoveDialog.class.getName());
+            ConfirmRemoveDialog confirmRemoveDialog =
+                    ConfirmRemoveDialog.newInstance(BiometricUtils.isConvenience(mFaceManager));
+            confirmRemoveDialog.setOnClickListener(mOnConfirmDialogClickListener);
+            confirmRemoveDialog.show(mActivity.getSupportFragmentManager(),
+                            ConfirmRemoveDialog.class.getName());
         }
     }
 
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintAuthenticateSidecar.java b/src/com/android/settings/biometrics/fingerprint/FingerprintAuthenticateSidecar.java
index 4264056..f3c8aba 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintAuthenticateSidecar.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintAuthenticateSidecar.java
@@ -21,6 +21,7 @@
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.os.CancellationSignal;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settings.core.InstrumentedFragment;
 
 /**
@@ -80,7 +81,6 @@
 
                 @Override
                 public void onAuthenticationError(int errMsgId, CharSequence errString) {
-                    mCancellationSignal = null;
                     if (mListener != null) {
                         mListener.onAuthenticationError(errMsgId, errString);
                     } else {
@@ -108,10 +108,12 @@
     }
 
     public void stopAuthentication() {
-        if (mCancellationSignal != null && !mCancellationSignal.isCanceled()) {
+        if (mCancellationSignal != null) {
+            // This will automatically check if the cancel has been sent and if so
+            // it won't send it again.
             mCancellationSignal.cancel();
+            mCancellationSignal = null;
         }
-        mCancellationSignal = null;
     }
 
     public void setListener(Listener listener) {
@@ -129,4 +131,9 @@
         }
         mListener = listener;
     }
+
+    @VisibleForTesting
+    boolean isCancelled() {
+        return mCancellationSignal == null || mCancellationSignal.isCanceled();
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index dbdb024..a62bd67 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -1101,9 +1101,9 @@
         }
     }
 
-    @SuppressWarnings("MissingSuperCall") // TODO: Fix me
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
         maybeHideSfpsText(newConfig);
         switch(newConfig.orientation) {
             case Configuration.ORIENTATION_LANDSCAPE: {
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index e47e9a8..505fe1c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -169,7 +169,8 @@
         private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm";
         private static final String KEY_HAS_FIRST_ENROLLED = "has_first_enrolled";
         private static final String KEY_IS_ENROLLING = "is_enrolled";
-        private static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH =
+        @VisibleForTesting
+        static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH =
                 "security_settings_require_screen_on_to_auth";
         private static final String KEY_FINGERPRINTS_ENROLLED_CATEGORY =
                 "security_settings_fingerprints_enrolled";
@@ -534,10 +535,6 @@
 
         private void addFingerprintPreferences(PreferenceGroup root) {
             final String fpPrefKey = addFingerprintItemPreferences(root);
-            if (isSfps()) {
-                scrollToPreference(fpPrefKey);
-                addFingerprintUnlockCategory();
-            }
             for (AbstractPreferenceController controller : mControllers) {
                 if (controller instanceof FingerprintSettingsPreferenceController) {
                     ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId);
@@ -545,6 +542,14 @@
                     ((FingerprintUnlockCategoryController) controller).setUserId(mUserId);
                 }
             }
+
+            // This needs to be after setting ids, otherwise
+            // |mRequireScreenOnToAuthPreferenceController.isChecked| is always checking the primary
+            // user instead of the user with |mUserId|.
+            if (isSfps()) {
+                scrollToPreference(fpPrefKey);
+                addFingerprintUnlockCategory();
+            }
             createFooterPreference(root);
         }
 
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java
index aa3f770..75251cf 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollProgressBarDrawable.java
@@ -202,6 +202,7 @@
             return;
         }
 
+        mShowingHelp = showingHelp;
         if (mShowingHelp) {
             if (mVibrator != null && mIsAccessibilityEnabled) {
                 mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
@@ -228,7 +229,6 @@
             }
         }
 
-        mShowingHelp = showingHelp;
         mRemainingSteps = remainingSteps;
         mTotalSteps = totalSteps;
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
new file mode 100644
index 0000000..2fbdedf
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractor.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
+import android.hardware.fingerprint.FingerprintManager.RemovalCallback
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.CancellationSignal
+import android.util.Log
+import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.password.ChooseLockSettingsHelper
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+private const val TAG = "FingerprintManagerInteractor"
+
+/** Encapsulates business logic related to managing fingerprints. */
+interface FingerprintManagerInteractor {
+  /** Returns the list of current fingerprints. */
+  val enrolledFingerprints: Flow<List<FingerprintViewModel>>
+
+  /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
+  val maxEnrollableFingerprints: Flow<Int>
+
+  /** Runs [FingerprintManager.authenticate] */
+  suspend fun authenticate(): FingerprintAuthAttemptViewModel
+
+  /**
+   * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
+   * challenge and challenge token. This info can be used for secure operations such as
+   * [FingerprintManager.enroll]
+   *
+   * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
+   * @return A [Pair] of the challenge and challenge token
+   */
+  suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
+
+  /** Returns true if a user can enroll a fingerprint false otherwise. */
+  fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean>
+
+  /**
+   * Removes the given fingerprint, returning true if it was successfully removed and false
+   * otherwise
+   */
+  suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean
+
+  /** Renames the given fingerprint if one exists */
+  suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String)
+
+  /** Indicates if the device has side fingerprint */
+  suspend fun hasSideFps(): Boolean
+
+  /** Indicates if the press to auth feature has been enabled */
+  suspend fun pressToAuthEnabled(): Boolean
+
+  /** Retrieves the sensor properties of a device */
+  suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal>
+}
+
+class FingerprintManagerInteractorImpl(
+  applicationContext: Context,
+  private val backgroundDispatcher: CoroutineDispatcher,
+  private val fingerprintManager: FingerprintManager,
+  private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
+  private val pressToAuthProvider: () -> Boolean,
+) : FingerprintManagerInteractor {
+
+  private val maxFingerprints =
+    applicationContext.resources.getInteger(
+      com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
+    )
+  private val applicationContext = applicationContext.applicationContext
+
+  override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
+    suspendCoroutine {
+      val callback = GenerateChallengeCallback { _, userId, challenge ->
+        val intent = Intent()
+        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
+        val challengeToken =
+          gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
+
+        gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
+        val p = Pair(challenge, challengeToken)
+        it.resume(p)
+      }
+      fingerprintManager.generateChallenge(applicationContext.userId, callback)
+    }
+
+  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+    emit(
+      fingerprintManager
+        .getEnrolledFingerprints(applicationContext.userId)
+        .map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) }
+        .toList()
+    )
+  }
+
+  override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
+    emit(numFingerprints < maxFingerprints)
+  }
+
+  override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
+
+  override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
+    val callback =
+      object : RemovalCallback() {
+        override fun onRemovalError(
+          fp: android.hardware.fingerprint.Fingerprint,
+          errMsgId: Int,
+          errString: CharSequence
+        ) {
+          it.resume(false)
+        }
+
+        override fun onRemovalSucceeded(
+          fp: android.hardware.fingerprint.Fingerprint?,
+          remaining: Int
+        ) {
+          it.resume(true)
+        }
+      }
+    fingerprintManager.remove(
+      android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
+      applicationContext.userId,
+      callback
+    )
+  }
+
+  override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+    withContext(backgroundDispatcher) {
+      fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
+    }
+  }
+
+  override suspend fun hasSideFps(): Boolean = suspendCancellableCoroutine {
+    it.resume(fingerprintManager.isPowerbuttonFps)
+  }
+
+  override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
+    it.resume(pressToAuthProvider())
+  }
+
+  override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
+    suspendCancellableCoroutine {
+      it.resume(fingerprintManager.sensorPropertiesInternal)
+    }
+
+  override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
+    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
+      val authenticationCallback =
+        object : FingerprintManager.AuthenticationCallback() {
+
+          override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+            super.onAuthenticationError(errorCode, errString)
+            if (c.isCompleted) {
+              Log.d(TAG, "framework sent down onAuthError after finish")
+              return
+            }
+            c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString()))
+          }
+
+          override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
+            super.onAuthenticationSucceeded(result)
+            if (c.isCompleted) {
+              Log.d(TAG, "framework sent down onAuthError after finish")
+              return
+            }
+            c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1))
+          }
+        }
+
+      val cancellationSignal = CancellationSignal()
+      c.invokeOnCancellation { cancellationSignal.cancel() }
+      fingerprintManager.authenticate(
+        null,
+        cancellationSignal,
+        authenticationCallback,
+        null,
+        applicationContext.userId
+      )
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/binder/FingerprintSettingsViewBinder.kt b/src/com/android/settings/biometrics/fingerprint2/ui/binder/FingerprintSettingsViewBinder.kt
new file mode 100644
index 0000000..d9f3e43
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/binder/FingerprintSettingsViewBinder.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.binder
+
+import android.hardware.fingerprint.FingerprintManager
+import android.util.Log
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.settings.biometrics.fingerprint2.ui.binder.FingerprintSettingsViewBinder.FingerprintView
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollAdditionalFingerprint
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintStateViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchedActivity
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.PreferenceViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintSettingsViewBinder"
+
+/** Binds a [FingerprintSettingsViewModel] to a [FingerprintView] */
+object FingerprintSettingsViewBinder {
+
+  interface FingerprintView {
+    /**
+     * Helper function to launch fingerprint enrollment(This should be the default behavior when a
+     * user enters their PIN/PATTERN/PASS and no fingerprints are enrolled).
+     */
+    fun launchFullFingerprintEnrollment(
+      userId: Int,
+      gateKeeperPasswordHandle: Long?,
+      challenge: Long?,
+      challengeToken: ByteArray?
+    )
+
+    /** Helper to launch an add fingerprint request */
+    fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?)
+    /**
+     * Helper function that will try and launch confirm lock, if that fails we will prompt user to
+     * choose a PIN/PATTERN/PASS.
+     */
+    fun launchConfirmOrChooseLock(userId: Int)
+
+    /** Used to indicate that FingerprintSettings is finished. */
+    fun finish()
+
+    /** Indicates what result should be set for the returning callee */
+    fun setResultExternal(resultCode: Int)
+    /** Indicates the settings UI should be shown */
+    fun showSettings(state: FingerprintStateViewModel)
+    /** Indicates that a user has been locked out */
+    fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error)
+    /** Indicates a fingerprint preference should be highlighted */
+    suspend fun highlightPref(fingerId: Int)
+    /** Indicates a user should be prompted to delete a fingerprint */
+    suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean
+    /** Indicates a user should be asked to renae ma dialog */
+    suspend fun askUserToRenameDialog(
+      fingerprintViewModel: FingerprintViewModel
+    ): Pair<FingerprintViewModel, String>?
+  }
+
+  fun bind(
+    view: FingerprintView,
+    viewModel: FingerprintSettingsViewModel,
+    navigationViewModel: FingerprintSettingsNavigationViewModel,
+    lifecycleScope: LifecycleCoroutineScope,
+  ) {
+
+    /** Result listener for launching enrollments **after** a user has reached the settings page. */
+
+    // Settings display flow
+    lifecycleScope.launch {
+      viewModel.fingerprintState.filterNotNull().collect { view.showSettings(it) }
+    }
+
+    // Dialog flow
+    lifecycleScope.launch {
+      viewModel.isShowingDialog.collectLatest {
+        if (it == null) {
+          return@collectLatest
+        }
+        when (it) {
+          is PreferenceViewModel.RenameDialog -> {
+            val willRename = view.askUserToRenameDialog(it.fingerprintViewModel)
+            if (willRename != null) {
+              Log.d(TAG, "renaming fingerprint $it")
+              viewModel.renameFingerprint(willRename.first, willRename.second)
+            }
+            viewModel.onRenameDialogFinished()
+          }
+          is PreferenceViewModel.DeleteDialog -> {
+            if (view.askUserToDeleteDialog(it.fingerprintViewModel)) {
+              Log.d(TAG, "deleting fingerprint $it")
+              viewModel.deleteFingerprint(it.fingerprintViewModel)
+            }
+            viewModel.onDeleteDialogFinished()
+          }
+        }
+      }
+    }
+
+    // Auth flow
+    lifecycleScope.launch {
+      viewModel.authFlow.filterNotNull().collect {
+        when (it) {
+          is FingerprintAuthAttemptViewModel.Success -> {
+            view.highlightPref(it.fingerId)
+          }
+          is FingerprintAuthAttemptViewModel.Error -> {
+            if (it.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
+              view.userLockout(it)
+            }
+          }
+        }
+      }
+    }
+
+    // Launch this on Dispatchers.Default and not main.
+    // Otherwise it takes too long for state transitions such as PIN/PATTERN/PASS
+    // to enrollment, which makes gives the user a janky experience.
+    lifecycleScope.launch(Dispatchers.Default) {
+      var settingsShowingJob: Job? = null
+      navigationViewModel.nextStep.filterNotNull().collect { nextStep ->
+        settingsShowingJob?.cancel()
+        settingsShowingJob = null
+        Log.d(TAG, "next step = $nextStep")
+        when (nextStep) {
+          is EnrollFirstFingerprint ->
+            view.launchFullFingerprintEnrollment(
+              nextStep.userId,
+              nextStep.gateKeeperPasswordHandle,
+              nextStep.challenge,
+              nextStep.challengeToken
+            )
+          is EnrollAdditionalFingerprint ->
+            view.launchAddFingerprint(nextStep.userId, nextStep.challengeToken)
+          is LaunchConfirmDeviceCredential -> view.launchConfirmOrChooseLock(nextStep.userId)
+          is FinishSettings -> {
+            Log.d(TAG, "Finishing due to ${nextStep.reason}")
+            view.finish()
+          }
+          is FinishSettingsWithResult -> {
+            Log.d(TAG, "Finishing with result ${nextStep.result} due to ${nextStep.reason}")
+            view.setResultExternal(nextStep.result)
+            view.finish()
+          }
+          is ShowSettings -> Log.d(TAG, "Showing settings")
+          is LaunchedActivity -> Log.d(TAG, "Launched activity, awaiting result")
+        }
+      }
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintDeletionDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintDeletionDialog.kt
new file mode 100644
index 0000000..42e2047
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintDeletionDialog.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.fragment
+
+import android.app.Dialog
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE
+import android.app.admin.DevicePolicyResources.UNDEFINED
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.os.UserManager
+import androidx.appcompat.app.AlertDialog
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val KEY_IS_LAST_FINGERPRINT = "IS_LAST_FINGERPRINT"
+
+class FingerprintDeletionDialog : InstrumentedDialogFragment() {
+  private lateinit var fingerprintViewModel: FingerprintViewModel
+  private var isLastFingerprint: Boolean = false
+  private lateinit var alertDialog: AlertDialog
+  lateinit var onClickListener: DialogInterface.OnClickListener
+  lateinit var onNegativeClickListener: DialogInterface.OnClickListener
+  lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+  override fun getMetricsCategory(): Int {
+    return SettingsEnums.DIALOG_FINGERPINT_EDIT
+  }
+
+  override fun onCancel(dialog: DialogInterface) {
+    onCancelListener.onCancel(dialog)
+  }
+
+  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+    val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
+    fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId)
+    isLastFingerprint = requireArguments().getBoolean(KEY_IS_LAST_FINGERPRINT)
+    val title = getString(R.string.fingerprint_delete_title, fingerprintViewModel.name)
+    var message = getString(R.string.fingerprint_v2_delete_message, fingerprintViewModel.name)
+    val context = requireContext()
+
+    if (isLastFingerprint) {
+      val isProfileChallengeUser = UserManager.get(context).isManagedProfile(context.userId)
+      val messageId =
+        if (isProfileChallengeUser) {
+          WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE
+        } else {
+          UNDEFINED
+        }
+      val defaultMessageId =
+        if (isProfileChallengeUser) {
+          R.string.fingerprint_last_delete_message_profile_challenge
+        } else {
+          R.string.fingerprint_last_delete_message
+        }
+      val devicePolicyManager = requireContext().getSystemService(DevicePolicyManager::class.java)
+      message =
+        devicePolicyManager?.resources?.getString(messageId) {
+          message + "\n\n" + context.getString(defaultMessageId)
+        }
+          ?: ""
+    }
+
+    alertDialog =
+      AlertDialog.Builder(requireActivity())
+        .setTitle(title)
+        .setMessage(message)
+        .setPositiveButton(
+          R.string.security_settings_fingerprint_enroll_dialog_delete,
+          onClickListener
+        )
+        .setNegativeButton(R.string.cancel, onNegativeClickListener)
+        .create()
+    return alertDialog
+  }
+
+  companion object {
+    private const val KEY_FINGERPRINT = "fingerprint"
+    suspend fun showInstance(
+      fp: FingerprintViewModel,
+      lastFingerprint: Boolean,
+      target: FingerprintSettingsV2Fragment,
+    ) = suspendCancellableCoroutine { continuation ->
+      val dialog = FingerprintDeletionDialog()
+      dialog.onClickListener = DialogInterface.OnClickListener { _, _ -> continuation.resume(true) }
+      dialog.onNegativeClickListener =
+        DialogInterface.OnClickListener { _, _ -> continuation.resume(false) }
+      dialog.onCancelListener = DialogInterface.OnCancelListener { continuation.resume(false) }
+
+      continuation.invokeOnCancellation { dialog.dismiss() }
+      val bundle = Bundle()
+      bundle.putObject(
+        KEY_FINGERPRINT,
+        android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId)
+      )
+      bundle.putBoolean(KEY_IS_LAST_FINGERPRINT, lastFingerprint)
+      dialog.arguments = bundle
+      dialog.show(target.parentFragmentManager, FingerprintDeletionDialog::class.java.toString())
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsPreference.kt b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsPreference.kt
new file mode 100644
index 0000000..e12785d
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsPreference.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.fragment
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceViewHolder
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settingslib.widget.TwoTargetPreference
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintSettingsPreference"
+
+class FingerprintSettingsPreference(
+  context: Context,
+  val fingerprintViewModel: FingerprintViewModel,
+  val fragment: FingerprintSettingsV2Fragment,
+  val isLastFingerprint: Boolean
+) : TwoTargetPreference(context) {
+  private lateinit var myView: View
+
+  init {
+    key = "FINGERPRINT_" + fingerprintViewModel.fingerId
+    Log.d(TAG, "FingerprintPreference $this with frag $fragment $key")
+    title = fingerprintViewModel.name
+    isPersistent = false
+    setIcon(R.drawable.ic_fingerprint_24dp)
+    setOnPreferenceClickListener {
+      fragment.lifecycleScope.launch { fragment.onPrefClicked(fingerprintViewModel) }
+      true
+    }
+  }
+
+  override fun onBindViewHolder(view: PreferenceViewHolder) {
+    super.onBindViewHolder(view)
+    myView = view.itemView
+    view.itemView.findViewById<View>(R.id.delete_button)?.setOnClickListener {
+      fragment.lifecycleScope.launch { fragment.onDeletePrefClicked(fingerprintViewModel) }
+    }
+  }
+
+  /** Highlights this dialog. */
+  suspend fun highlight() {
+    fragment.activity?.getDrawable(R.drawable.preference_highlight)?.let { highlight ->
+      val centerX: Float = myView.width / 2.0f
+      val centerY: Float = myView.height / 2.0f
+      highlight.setHotspot(centerX, centerY)
+      myView.background = highlight
+      myView.isPressed = true
+      myView.isPressed = false
+      delay(300)
+      myView.background = null
+    }
+  }
+
+  override fun getSecondTargetResId(): Int {
+    return R.layout.preference_widget_delete
+  }
+
+  suspend fun askUserToDeleteDialog(): Boolean {
+    return FingerprintDeletionDialog.showInstance(fingerprintViewModel, isLastFingerprint, fragment)
+  }
+
+  suspend fun askUserToRenameDialog(): Pair<FingerprintViewModel, String>? {
+    return FingerprintSettingsRenameDialog.showInstance(fingerprintViewModel, fragment)
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsRenameDialog.kt b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsRenameDialog.kt
new file mode 100644
index 0000000..9542ed8
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsRenameDialog.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.fragment
+
+import android.app.Dialog
+import android.app.settings.SettingsEnums
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.InputFilter
+import android.text.Spanned
+import android.text.TextUtils
+import android.util.Log
+import android.widget.ImeAwareEditText
+import androidx.appcompat.app.AlertDialog
+import com.android.settings.R
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import kotlin.coroutines.resume
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "FingerprintSettingsRenameDialog"
+
+class FingerprintSettingsRenameDialog : InstrumentedDialogFragment() {
+  lateinit var onClickListener: DialogInterface.OnClickListener
+  lateinit var onCancelListener: DialogInterface.OnCancelListener
+
+  override fun onCancel(dialog: DialogInterface) {
+    Log.d(TAG, "onCancel $dialog")
+    onCancelListener.onCancel(dialog)
+  }
+
+  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+    Log.d(TAG, "onCreateDialog $this")
+    val fp = requireArguments().get(KEY_FINGERPRINT) as android.hardware.fingerprint.Fingerprint
+    val fingerprintViewModel = FingerprintViewModel(fp.name.toString(), fp.biometricId, fp.deviceId)
+
+    val context = requireContext()
+    val alertDialog =
+      AlertDialog.Builder(context)
+        .setView(R.layout.fingerprint_rename_dialog)
+        .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, onClickListener)
+        .create()
+    alertDialog.setOnShowListener {
+      (dialog?.findViewById(R.id.fingerprint_rename_field) as ImeAwareEditText?)?.apply {
+        val name = fingerprintViewModel.name
+        setText(name)
+        filters = this@FingerprintSettingsRenameDialog.getFilters()
+        selectAll()
+        requestFocus()
+        scheduleShowSoftInput()
+      }
+    }
+
+    return alertDialog
+  }
+
+  private fun getFilters(): Array<InputFilter> {
+    val filter: InputFilter =
+      object : InputFilter {
+
+        override fun filter(
+          source: CharSequence,
+          start: Int,
+          end: Int,
+          dest: Spanned?,
+          dstart: Int,
+          dend: Int
+        ): CharSequence? {
+          for (index in start until end) {
+            val c = source[index]
+            // KXMLSerializer does not allow these characters,
+            // see KXmlSerializer.java:162.
+            if (c.code < 0x20) {
+              return ""
+            }
+          }
+          return null
+        }
+      }
+    return arrayOf(filter)
+  }
+
+  override fun getMetricsCategory(): Int {
+    return SettingsEnums.DIALOG_FINGERPINT_EDIT
+  }
+
+  companion object {
+    private const val KEY_FINGERPRINT = "fingerprint"
+
+    suspend fun showInstance(fp: FingerprintViewModel, target: FingerprintSettingsV2Fragment) =
+      suspendCancellableCoroutine { continuation ->
+        val dialog = FingerprintSettingsRenameDialog()
+        val onClick =
+          DialogInterface.OnClickListener { _, _ ->
+            val dialogTextField = dialog.requireDialog()
+                .requireViewById(R.id.fingerprint_rename_field) as ImeAwareEditText
+            val newName = dialogTextField.text.toString()
+            if (!TextUtils.equals(newName, fp.name)) {
+              Log.d(TAG, "rename $fp.name to $newName for $dialog")
+              continuation.resume(Pair(fp, newName))
+            } else {
+              continuation.resume(null)
+            }
+          }
+
+        dialog.onClickListener = onClick
+        dialog.onCancelListener =
+          DialogInterface.OnCancelListener {
+            Log.d(TAG, "onCancelListener clicked $dialog")
+            continuation.resume(null)
+          }
+
+        continuation.invokeOnCancellation {
+          Log.d(TAG, "invokeOnCancellation $dialog")
+          dialog.dismiss()
+        }
+
+        val bundle = Bundle()
+        bundle.putObject(
+          KEY_FINGERPRINT,
+          android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId)
+        )
+        dialog.arguments = bundle
+        Log.d(TAG, "showing dialog $dialog")
+        dialog.show(
+          target.parentFragmentManager,
+          FingerprintSettingsRenameDialog::class.java.toString()
+        )
+      }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsV2Fragment.kt
new file mode 100644
index 0000000..b82f7c1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/fragment/FingerprintSettingsV2Fragment.kt
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.fragment
+
+import android.app.Activity
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION
+import android.app.settings.SettingsEnums
+import android.content.Context.FINGERPRINT_SERVICE
+import android.content.Intent
+import android.hardware.fingerprint.FingerprintManager
+import android.os.Bundle
+import android.provider.Settings.Secure
+import android.text.TextUtils
+import android.util.FeatureFlagUtils
+import android.util.Log
+import android.view.View
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import com.android.internal.widget.LockPatternUtils
+import com.android.settings.R
+import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
+import com.android.settings.biometrics.BiometricEnrollBase
+import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
+import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY
+import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
+import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
+import com.android.settings.biometrics.fingerprint2.ui.binder.FingerprintSettingsViewBinder
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintStateViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.core.SettingsBaseActivity
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment
+import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.password.ChooseLockGeneric
+import com.android.settings.password.ChooseLockSettingsHelper
+import com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE
+import com.android.settingslib.HelpUtils
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.transition.SettingsTransitionHelper
+import com.android.settingslib.widget.FooterPreference
+import com.google.android.setupdesign.util.DeviceHelper
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintSettingsV2Fragment"
+private const val KEY_FINGERPRINTS_ENROLLED_CATEGORY = "security_settings_fingerprints_enrolled"
+private const val KEY_FINGERPRINT_SIDE_FPS_CATEGORY =
+  "security_settings_fingerprint_unlock_category"
+private const val KEY_FINGERPRINT_ADD = "key_fingerprint_add"
+private const val KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH =
+  "security_settings_require_screen_on_to_auth"
+private const val KEY_FINGERPRINT_FOOTER = "security_settings_fingerprint_footer"
+
+/**
+ * A class responsible for showing FingerprintSettings. Typical activity Flows are
+ * 1. Settings > FingerprintSettings > PIN/PATTERN/PASS -> FingerprintSettings
+ * 2. FingerprintSettings -> FingerprintEnrollment fow
+ *
+ * This page typically allows for
+ * 1. Fingerprint deletion
+ * 2. Fingerprint enrollment
+ * 3. Renaming a fingerprint
+ * 4. Enabling/Disabling a feature
+ */
+class FingerprintSettingsV2Fragment :
+  DashboardFragment(), FingerprintSettingsViewBinder.FingerprintView {
+  private lateinit var settingsViewModel: FingerprintSettingsViewModel
+  private lateinit var navigationViewModel: FingerprintSettingsNavigationViewModel
+
+  /** Result listener for ChooseLock activity flow. */
+  private val confirmDeviceResultListener =
+    registerForActivityResult(StartActivityForResult()) { result ->
+      val resultCode = result.resultCode
+      val data = result.data
+      onConfirmDevice(resultCode, data)
+    }
+
+  /** Result listener for launching enrollments **after** a user has reached the settings page. */
+  private val launchAdditionalFingerprintListener: ActivityResultLauncher<Intent> =
+    registerForActivityResult(StartActivityForResult()) { result ->
+      lifecycleScope.launch {
+        val resultCode = result.resultCode
+        Log.d(TAG, "onEnrollAdditionalFingerprint($resultCode)")
+
+        if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
+          navigationViewModel.onEnrollAdditionalFailure()
+        } else {
+          navigationViewModel.onEnrollSuccess()
+        }
+      }
+    }
+
+  /** Initial listener for the first enrollment request */
+  private val launchFirstEnrollmentListener: ActivityResultLauncher<Intent> =
+    registerForActivityResult(StartActivityForResult()) { result ->
+      lifecycleScope.launch {
+        val resultCode = result.resultCode
+        val data = result.data
+
+        Log.d(TAG, "onEnrollFirstFingerprint($resultCode, $data)")
+        if (resultCode != RESULT_FINISHED || data == null) {
+          if (resultCode == BiometricEnrollBase.RESULT_TIMEOUT) {
+            navigationViewModel.onEnrollFirstFailure(
+              "Received RESULT_TIMEOUT when enrolling",
+              resultCode
+            )
+          } else {
+            navigationViewModel.onEnrollFirstFailure(
+              "Incorrect resultCode or data was null",
+              resultCode
+            )
+          }
+        } else {
+          val token = data.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
+          val challenge = data.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE) as Long?
+          navigationViewModel.onEnrollFirst(token, challenge)
+        }
+      }
+    }
+
+  override fun userLockout(authAttemptViewModel: FingerprintAuthAttemptViewModel.Error) {
+    Toast.makeText(activity, authAttemptViewModel.message, Toast.LENGTH_SHORT).show()
+  }
+
+  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+    // This is needed to support ChooseLockSettingBuilder...show(). All other activity
+    // calls should use the registerForActivity method call.
+    super.onActivityResult(requestCode, resultCode, data)
+    onConfirmDevice(resultCode, data)
+  }
+
+  override fun onCreate(icicle: Bundle?) {
+    super.onCreate(icicle)
+
+    if (icicle != null) {
+      Log.d(TAG, "onCreateWithSavedState")
+    } else {
+      Log.d(TAG, "onCreate()")
+    }
+
+    if (
+      !FeatureFlagUtils.isEnabled(
+        context,
+        FeatureFlagUtils.SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS
+      )
+    ) {
+      Log.d(TAG, "Finishing due to feature not being enabled")
+      finish()
+      return
+    }
+
+    val context = requireContext()
+    val userId = context.userId
+
+    preferenceScreen.isVisible = false
+
+    val fingerprintManager = context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager
+
+    val backgroundDispatcher = Dispatchers.IO
+    val activity = requireActivity()
+    val userHandle = activity.user.identifier
+
+    val interactor =
+      FingerprintManagerInteractorImpl(
+        context.applicationContext,
+        backgroundDispatcher,
+        fingerprintManager,
+        GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext))
+      ) {
+        var toReturn: Int =
+          Secure.getIntForUser(
+            context.contentResolver,
+            Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+            -1,
+            userHandle,
+          )
+        if (toReturn == -1) {
+          toReturn =
+            if (
+              context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
+            ) {
+              1
+            } else {
+              0
+            }
+          Secure.putIntForUser(
+            context.contentResolver,
+            Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+            toReturn,
+            userHandle
+          )
+        }
+
+        toReturn == 1
+      }
+
+    val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
+    val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
+
+    navigationViewModel =
+      ViewModelProvider(
+        this,
+        FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
+          userId,
+          interactor,
+          backgroundDispatcher,
+          token,
+          challenge
+        )
+      )[FingerprintSettingsNavigationViewModel::class.java]
+
+    settingsViewModel =
+      ViewModelProvider(
+        this,
+        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+          userId,
+          interactor,
+          backgroundDispatcher,
+          navigationViewModel,
+        )
+      )[FingerprintSettingsViewModel::class.java]
+
+    FingerprintSettingsViewBinder.bind(
+      this,
+      settingsViewModel,
+      navigationViewModel,
+      lifecycleScope,
+    )
+  }
+
+  override fun getMetricsCategory(): Int {
+    return SettingsEnums.FINGERPRINT
+  }
+
+  override fun getPreferenceScreenResId(): Int {
+    return R.xml.security_settings_fingerprint_limbo
+  }
+
+  override fun getLogTag(): String {
+    return TAG
+  }
+
+  override fun onStop() {
+    super.onStop()
+    navigationViewModel.maybeFinishActivity(requireActivity().isChangingConfigurations)
+  }
+
+  override fun onPause() {
+    super.onPause()
+    settingsViewModel.shouldAuthenticate(false)
+    val transaction = parentFragmentManager.beginTransaction()
+    for (frag in parentFragmentManager.fragments) {
+      if (frag is InstrumentedDialogFragment) {
+        Log.d(TAG, "removing dialog settings fragment $frag")
+        frag.dismiss()
+        transaction.remove(frag)
+      }
+    }
+    transaction.commit()
+  }
+
+  override fun onResume() {
+    super.onResume()
+    settingsViewModel.shouldAuthenticate(true)
+  }
+
+  /** Used to indicate that preference has been clicked */
+  fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
+    Log.d(TAG, "onPrefClicked(${fingerprintViewModel})")
+    settingsViewModel.onPrefClicked(fingerprintViewModel)
+  }
+
+  /** Used to indicate that a delete pref has been clicked */
+  fun onDeletePrefClicked(fingerprintViewModel: FingerprintViewModel) {
+    Log.d(TAG, "onDeletePrefClicked(${fingerprintViewModel})")
+    settingsViewModel.onDeleteClicked(fingerprintViewModel)
+  }
+
+  override fun showSettings(state: FingerprintStateViewModel) {
+    val category =
+      this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
+        as PreferenceCategory?
+
+    category?.removeAll()
+
+    state.fingerprintViewModels.forEach { fingerprint ->
+      category?.addPreference(
+        FingerprintSettingsPreference(
+          requireContext(),
+          fingerprint,
+          this@FingerprintSettingsV2Fragment,
+          state.fingerprintViewModels.size == 1,
+        )
+      )
+    }
+    category?.isVisible = true
+
+    createFingerprintsFooterPreference(state.canEnroll, state.maxFingerprints)
+    preferenceScreen.isVisible = true
+
+    val sideFpsPref =
+      this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_SIDE_FPS_CATEGORY)
+        as PreferenceCategory?
+    sideFpsPref?.isVisible = false
+
+    if (state.hasSideFps) {
+      sideFpsPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
+      val otherPref =
+        this@FingerprintSettingsV2Fragment.findPreference(
+          KEY_FINGERPRINT_SIDE_FPS_SCREEN_ON_TO_AUTH
+        ) as Preference?
+      otherPref?.isVisible = state.fingerprintViewModels.isNotEmpty()
+    }
+    addFooter(state.hasSideFps)
+  }
+  private fun addFooter(hasSideFps: Boolean) {
+    val footer =
+      this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINT_FOOTER)
+        as PreferenceCategory?
+    val admin =
+      RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled(
+        activity,
+        DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT,
+        requireActivity().userId
+      )
+    val activity = requireActivity()
+    val helpIntent =
+      HelpUtils.getHelpIntent(activity, getString(helpResource), activity::class.java.name)
+    val learnMoreClickListener =
+      View.OnClickListener { v: View? -> activity.startActivityForResult(helpIntent, 0) }
+
+    class FooterColumn {
+      var title: CharSequence? = null
+      var learnMoreOverrideText: CharSequence? = null
+      var learnMoreOnClickListener: View.OnClickListener? = null
+    }
+
+    var footerColumns = mutableListOf<FooterColumn>()
+    if (admin != null) {
+      val devicePolicyManager = getSystemService(DevicePolicyManager::class.java)
+      val column1 = FooterColumn()
+      column1.title =
+        devicePolicyManager.resources.getString(FINGERPRINT_UNLOCK_DISABLED_EXPLANATION) {
+          getString(R.string.security_fingerprint_disclaimer_lockscreen_disabled_1)
+        }
+
+      column1.learnMoreOnClickListener =
+        View.OnClickListener { _ ->
+          RestrictedLockUtils.sendShowAdminSupportDetailsIntent(activity, admin)
+        }
+      column1.learnMoreOverrideText = getText(R.string.admin_support_more_info)
+      footerColumns.add(column1)
+      val column2 = FooterColumn()
+      column2.title = getText(R.string.security_fingerprint_disclaimer_lockscreen_disabled_2)
+      if (hasSideFps) {
+        column2.learnMoreOverrideText =
+          getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
+      }
+      column2.learnMoreOnClickListener = learnMoreClickListener
+      footerColumns.add(column2)
+    } else {
+      val column = FooterColumn()
+      column.title =
+        getString(
+          R.string.security_settings_fingerprint_enroll_introduction_v3_message,
+          DeviceHelper.getDeviceName(requireActivity())
+        )
+      column.learnMoreOnClickListener = learnMoreClickListener
+      if (hasSideFps) {
+        column.learnMoreOverrideText =
+          getText(R.string.security_settings_fingerprint_settings_footer_learn_more)
+      }
+      footerColumns.add(column)
+    }
+
+    footer?.removeAll()
+    for (i in 0 until footerColumns.size) {
+      val column = footerColumns[i]
+      val footerPrefToAdd: FooterPreference =
+        FooterPreference.Builder(requireContext()).setTitle(column.title).build()
+      if (i > 0) {
+        footerPrefToAdd.setIconVisibility(View.GONE)
+      }
+      if (column.learnMoreOnClickListener != null) {
+        footerPrefToAdd.setLearnMoreAction(column.learnMoreOnClickListener)
+        if (!TextUtils.isEmpty(column.learnMoreOverrideText)) {
+          footerPrefToAdd.setLearnMoreText(column.learnMoreOverrideText)
+        }
+      }
+      footer?.addPreference(footerPrefToAdd)
+    }
+  }
+
+  override suspend fun askUserToDeleteDialog(fingerprintViewModel: FingerprintViewModel): Boolean {
+    Log.d(TAG, "showing delete dialog for (${fingerprintViewModel})")
+
+    try {
+      val willDelete =
+        fingerprintPreferences()
+          .first { it?.fingerprintViewModel == fingerprintViewModel }
+          ?.askUserToDeleteDialog()
+          ?: false
+      if (willDelete) {
+        mMetricsFeatureProvider.action(
+          context,
+          SettingsEnums.ACTION_FINGERPRINT_DELETE,
+          fingerprintViewModel.fingerId
+        )
+      }
+      return willDelete
+    } catch (exception: Exception) {
+      Log.d(TAG, "askUserToDeleteDialog exception $exception")
+      return false
+    }
+  }
+
+  override suspend fun askUserToRenameDialog(
+    fingerprintViewModel: FingerprintViewModel
+  ): Pair<FingerprintViewModel, String>? {
+    Log.d(TAG, "showing rename dialog for (${fingerprintViewModel})")
+    try {
+      val toReturn =
+        fingerprintPreferences()
+          .first { it?.fingerprintViewModel == fingerprintViewModel }
+          ?.askUserToRenameDialog()
+      if (toReturn != null) {
+        mMetricsFeatureProvider.action(
+          context,
+          SettingsEnums.ACTION_FINGERPRINT_RENAME,
+          toReturn.first.fingerId
+        )
+      }
+      return toReturn
+    } catch (exception: Exception) {
+      Log.d(TAG, "askUserToRenameDialog exception $exception")
+      return null
+    }
+  }
+
+  override suspend fun highlightPref(fingerId: Int) {
+    fingerprintPreferences()
+      .first { pref -> pref?.fingerprintViewModel?.fingerId == fingerId }
+      ?.highlight()
+  }
+
+  override fun launchConfirmOrChooseLock(userId: Int) {
+    lifecycleScope.launch(Dispatchers.Default) {
+      navigationViewModel.setStepToLaunched()
+      val intent = Intent()
+      val builder =
+        ChooseLockSettingsHelper.Builder(requireActivity(), this@FingerprintSettingsV2Fragment)
+      val launched =
+        builder
+          .setRequestCode(CONFIRM_REQUEST)
+          .setTitle(getString(R.string.security_settings_fingerprint_preference_title))
+          .setRequestGatekeeperPasswordHandle(true)
+          .setUserId(userId)
+          .setForegroundOnly(true)
+          .setReturnCredentials(true)
+          .show()
+      if (!launched) {
+        intent.setClassName(SETTINGS_PACKAGE_NAME, ChooseLockGeneric::class.java.name)
+        intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true)
+        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true)
+        intent.putExtra(Intent.EXTRA_USER_ID, userId)
+        confirmDeviceResultListener.launch(intent)
+      }
+    }
+  }
+
+  override fun launchFullFingerprintEnrollment(
+    userId: Int,
+    gateKeeperPasswordHandle: Long?,
+    challenge: Long?,
+    challengeToken: ByteArray?,
+  ) {
+    navigationViewModel.setStepToLaunched()
+    Log.d(TAG, "launchFullFingerprintEnrollment")
+    val intent = Intent()
+    intent.setClassName(
+      SETTINGS_PACKAGE_NAME,
+      FingerprintEnrollIntroductionInternal::class.java.name
+    )
+    intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true)
+    intent.putExtra(
+      SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
+      SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE
+    )
+
+    intent.putExtra(Intent.EXTRA_USER_ID, userId)
+
+    if (gateKeeperPasswordHandle != null) {
+      intent.putExtra(EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
+    } else {
+      intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken)
+      intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge)
+    }
+    launchFirstEnrollmentListener.launch(intent)
+  }
+
+  override fun setResultExternal(resultCode: Int) {
+    setResult(resultCode)
+  }
+
+  override fun launchAddFingerprint(userId: Int, challengeToken: ByteArray?) {
+    navigationViewModel.setStepToLaunched()
+    val intent = Intent()
+    intent.setClassName(
+      SETTINGS_PACKAGE_NAME,
+      FingerprintEnrollEnrolling::class.qualifiedName.toString()
+    )
+    intent.putExtra(Intent.EXTRA_USER_ID, userId)
+    intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, challengeToken)
+    launchAdditionalFingerprintListener.launch(intent)
+  }
+
+  private fun onConfirmDevice(resultCode: Int, data: Intent?) {
+    val wasSuccessful = resultCode == RESULT_FINISHED || resultCode == Activity.RESULT_OK
+    val gateKeeperPasswordHandle = data?.getExtra(EXTRA_KEY_GK_PW_HANDLE) as Long?
+    lifecycleScope.launch {
+      navigationViewModel.onConfirmDevice(wasSuccessful, gateKeeperPasswordHandle)
+    }
+  }
+
+  private fun createFingerprintsFooterPreference(canEnroll: Boolean, maxFingerprints: Int) {
+    val pref = this@FingerprintSettingsV2Fragment.findPreference<Preference>(KEY_FINGERPRINT_ADD)
+    val maxSummary = context?.getString(R.string.fingerprint_add_max, maxFingerprints) ?: ""
+    pref?.summary = maxSummary
+    pref?.isEnabled = canEnroll
+    pref?.setOnPreferenceClickListener {
+      navigationViewModel.onAddFingerprintClicked()
+      true
+    }
+    pref?.isVisible = true
+  }
+
+  private fun fingerprintPreferences(): List<FingerprintSettingsPreference?> {
+    val category =
+      this@FingerprintSettingsV2Fragment.findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY)
+        as PreferenceCategory?
+
+    return category?.let { cat ->
+      cat.childrenToList().map { it as FingerprintSettingsPreference? }
+    }
+      ?: emptyList()
+  }
+
+  private fun PreferenceCategory.childrenToList(): List<Preference> {
+    val mutable: MutableList<Preference> = mutableListOf()
+    for (i in 0 until this.preferenceCount) {
+      mutable.add(this.getPreference(i))
+    }
+    return mutable.toList()
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt
new file mode 100644
index 0000000..a3a5d3c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsNavigationViewModel.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.BiometricEnrollBase
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+/** A Viewmodel that represents the navigation of the FingerprintSettings activity. */
+class FingerprintSettingsNavigationViewModel(
+  private val userId: Int,
+  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+  private val backgroundDispatcher: CoroutineDispatcher,
+  tokenInit: ByteArray?,
+  challengeInit: Long?,
+) : ViewModel() {
+
+  private var token = tokenInit
+  private var challenge = challengeInit
+
+  private val _nextStep: MutableStateFlow<NextStepViewModel?> = MutableStateFlow(null)
+  /** This flow represents the high level state for the FingerprintSettingsV2Fragment. */
+  val nextStep: StateFlow<NextStepViewModel?> = _nextStep.asStateFlow()
+
+  init {
+    if (challengeInit == null || tokenInit == null) {
+      _nextStep.update { LaunchConfirmDeviceCredential(userId) }
+    } else {
+      viewModelScope.launch { showSettingsHelper() }
+    }
+  }
+
+  /** Used to indicate that FingerprintSettings is complete. */
+  fun finish() {
+    _nextStep.update { null }
+  }
+
+  /** Used to finish settings in certain cases. */
+  fun maybeFinishActivity(changingConfig: Boolean) {
+    val isConfirmingOrEnrolling =
+      _nextStep.value is LaunchConfirmDeviceCredential ||
+        _nextStep.value is EnrollAdditionalFingerprint ||
+        _nextStep.value is EnrollFirstFingerprint ||
+        _nextStep.value is LaunchedActivity
+    if (!isConfirmingOrEnrolling && !changingConfig)
+      _nextStep.update {
+        FinishSettingsWithResult(BiometricEnrollBase.RESULT_TIMEOUT, "onStop finishing settings")
+      }
+  }
+
+  /** Used to indicate that we have launched another activity and we should await its result. */
+  fun setStepToLaunched() {
+    _nextStep.update { LaunchedActivity }
+  }
+
+  /** Indicates a successful enroll has occurred */
+  fun onEnrollSuccess() {
+    showSettingsHelper()
+  }
+
+  /** Add fingerprint clicked */
+  fun onAddFingerprintClicked() {
+    _nextStep.update { EnrollAdditionalFingerprint(userId, token) }
+  }
+
+  /** Enrolling of an additional fingerprint failed */
+  fun onEnrollAdditionalFailure() {
+    launchFinishSettings("Failed to enroll additional fingerprint")
+  }
+
+  /** The first fingerprint enrollment failed */
+  fun onEnrollFirstFailure(reason: String) {
+    launchFinishSettings(reason)
+  }
+
+  /** The first fingerprint enrollment failed with a result code */
+  fun onEnrollFirstFailure(reason: String, resultCode: Int) {
+    launchFinishSettings(reason, resultCode)
+  }
+
+  /** Notifies that a users first enrollment succeeded. */
+  fun onEnrollFirst(theToken: ByteArray?, theChallenge: Long?) {
+    if (theToken == null) {
+      launchFinishSettings("Error, empty token")
+      return
+    }
+    if (theChallenge == null) {
+      launchFinishSettings("Error, empty keyChallenge")
+      return
+    }
+    token = theToken!!
+    challenge = theChallenge!!
+
+    showSettingsHelper()
+  }
+
+  /**
+   * Indicates to the view model that a confirm device credential action has been completed with a
+   * [theGateKeeperPasswordHandle] which will be used for [FingerprintManager] operations such as
+   * [FingerprintManager.enroll].
+   */
+  suspend fun onConfirmDevice(wasSuccessful: Boolean, theGateKeeperPasswordHandle: Long?) {
+    if (!wasSuccessful) {
+      launchFinishSettings("ConfirmDeviceCredential was unsuccessful")
+      return
+    }
+    if (theGateKeeperPasswordHandle == null) {
+      launchFinishSettings("ConfirmDeviceCredential gatekeeper password was null")
+      return
+    }
+
+    launchEnrollNextStep(theGateKeeperPasswordHandle)
+  }
+
+  private fun showSettingsHelper() {
+    _nextStep.update { ShowSettings }
+  }
+
+  private suspend fun launchEnrollNextStep(gateKeeperPasswordHandle: Long?) {
+    fingerprintManagerInteractor.enrolledFingerprints.collect {
+      if (it.isEmpty()) {
+        _nextStep.update { EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, null, null) }
+      } else {
+        viewModelScope.launch(backgroundDispatcher) {
+          val challengePair =
+            fingerprintManagerInteractor.generateChallenge(gateKeeperPasswordHandle!!)
+          challenge = challengePair.first
+          token = challengePair.second
+
+          showSettingsHelper()
+        }
+      }
+    }
+  }
+
+  private fun launchFinishSettings(reason: String) {
+    _nextStep.update { FinishSettings(reason) }
+  }
+
+  private fun launchFinishSettings(reason: String, errorCode: Int) {
+    _nextStep.update { FinishSettingsWithResult(errorCode, reason) }
+  }
+  class FingerprintSettingsNavigationModelFactory(
+    private val userId: Int,
+    private val interactor: FingerprintManagerInteractor,
+    private val backgroundDispatcher: CoroutineDispatcher,
+    private val token: ByteArray?,
+    private val challenge: Long?,
+  ) : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+
+      return FingerprintSettingsNavigationViewModel(
+        userId,
+        interactor,
+        backgroundDispatcher,
+        token,
+        challenge,
+      )
+        as T
+    }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
new file mode 100644
index 0000000..554f336
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintSettingsViewModel.kt
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.last
+import kotlinx.coroutines.flow.sample
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+private const val TAG = "FingerprintSettingsViewModel"
+private const val DEBUG = false
+
+/** Models the UI state for fingerprint settings. */
+class FingerprintSettingsViewModel(
+  private val userId: Int,
+  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
+  private val backgroundDispatcher: CoroutineDispatcher,
+  private val navigationViewModel: FingerprintSettingsNavigationViewModel,
+) : ViewModel() {
+
+  private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+  private val fingerprintSensorPropertiesInternal:
+    MutableStateFlow<List<FingerprintSensorPropertiesInternal>?> =
+    MutableStateFlow(null)
+
+  private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
+  val isShowingDialog =
+    _isShowingDialog.combine(navigationViewModel.nextStep) { dialogFlow, nextStep ->
+      if (nextStep is ShowSettings) {
+        return@combine dialogFlow
+      } else {
+        return@combine null
+      }
+    }
+
+  init {
+    viewModelScope.launch {
+      fingerprintSensorPropertiesInternal.update {
+        fingerprintManagerInteractor.sensorPropertiesInternal()
+      }
+    }
+
+    viewModelScope.launch {
+      navigationViewModel.nextStep.filterNotNull().collect {
+        _isShowingDialog.update { null }
+        if (it is ShowSettings) {
+          // reset state
+          updateSettingsData()
+        }
+      }
+    }
+  }
+
+  private val _fingerprintStateViewModel: MutableStateFlow<FingerprintStateViewModel?> =
+    MutableStateFlow(null)
+  val fingerprintState: Flow<FingerprintStateViewModel?> =
+    _fingerprintStateViewModel.combineTransform(navigationViewModel.nextStep) {
+      settingsShowingViewModel,
+      currStep ->
+      if (currStep != null && currStep is ShowSettings) {
+        emit(settingsShowingViewModel)
+      }
+    }
+
+  private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptViewModel.Error?> =
+    MutableStateFlow(null)
+
+  private val _authSucceeded: MutableSharedFlow<FingerprintAuthAttemptViewModel.Success?> =
+    MutableSharedFlow()
+
+  private val attemptsSoFar: MutableStateFlow<Int> = MutableStateFlow(0)
+
+  /**
+   * This is a very tricky flow. The current fingerprint manager APIs are not robust, and a proper
+   * implementation would take quite a lot of code to implement, it might be easier to rewrite
+   * FingerprintManager.
+   *
+   * The hack to note is the sample(400), if we call authentications in too close of proximity
+   * without waiting for a response, the fingerprint manager will send us the results of the
+   * previous attempt.
+   */
+  private val canAuthenticate: Flow<Boolean> =
+    combine(
+        _isShowingDialog,
+        navigationViewModel.nextStep,
+        _consumerShouldAuthenticate,
+        _fingerprintStateViewModel,
+        _isLockedOut,
+        attemptsSoFar,
+        fingerprintSensorPropertiesInternal
+      ) { dialogShowing, step, resume, fingerprints, isLockedOut, attempts, sensorProps ->
+        if (DEBUG) {
+          Log.d(
+            TAG,
+            "canAuthenticate(isShowingDialog=${dialogShowing != null}," +
+              "nextStep=${step}," +
+              "resumed=${resume}," +
+              "fingerprints=${fingerprints}," +
+              "lockedOut=${isLockedOut}," +
+              "attempts=${attempts}," +
+              "sensorProps=${sensorProps}"
+          )
+        }
+        if (sensorProps.isNullOrEmpty()) {
+          return@combine false
+        }
+        val sensorType = sensorProps[0].sensorType
+        if (listOf(TYPE_UDFPS_OPTICAL, TYPE_UDFPS_ULTRASONIC).contains(sensorType)) {
+          return@combine false
+        }
+
+        if (step != null && step is ShowSettings) {
+          if (fingerprints?.fingerprintViewModels?.isNotEmpty() == true) {
+            return@combine dialogShowing == null && isLockedOut == null && resume && attempts < 15
+          }
+        }
+        false
+      }
+      .sample(400)
+      .distinctUntilChanged()
+
+  /** Represents a consistent stream of authentication attempts. */
+  val authFlow: Flow<FingerprintAuthAttemptViewModel> =
+    canAuthenticate
+      .transformLatest {
+        try {
+          Log.d(TAG, "canAuthenticate $it")
+          while (it && navigationViewModel.nextStep.value is ShowSettings) {
+            Log.d(TAG, "canAuthenticate authing")
+            attemptingAuth()
+            when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
+              is FingerprintAuthAttemptViewModel.Success -> {
+                onAuthSuccess(authAttempt)
+                emit(authAttempt)
+              }
+              is FingerprintAuthAttemptViewModel.Error -> {
+                if (authAttempt.error == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
+                  lockout(authAttempt)
+                  emit(authAttempt)
+                  return@transformLatest
+                }
+              }
+            }
+          }
+        } catch (exception: Exception) {
+          Log.d(TAG, "shouldAuthenticate exception $exception")
+        }
+      }
+      .flowOn(backgroundDispatcher)
+
+  /** The rename dialog has finished */
+  fun onRenameDialogFinished() {
+    _isShowingDialog.update { null }
+  }
+
+  /** The delete dialog has finished */
+  fun onDeleteDialogFinished() {
+    _isShowingDialog.update { null }
+  }
+
+  override fun toString(): String {
+    return "userId: $userId\n" + "fingerprintState: ${_fingerprintStateViewModel.value}\n"
+  }
+
+  /** The fingerprint delete button has been clicked. */
+  fun onDeleteClicked(fingerprintViewModel: FingerprintViewModel) {
+    viewModelScope.launch {
+      if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
+        _isShowingDialog.tryEmit(PreferenceViewModel.DeleteDialog(fingerprintViewModel))
+      } else {
+        Log.d(TAG, "Ignoring onDeleteClicked due to dialog showing ${_isShowingDialog.value}")
+      }
+    }
+  }
+
+  /** The rename fingerprint dialog has been clicked. */
+  fun onPrefClicked(fingerprintViewModel: FingerprintViewModel) {
+    viewModelScope.launch {
+      if (_isShowingDialog.value == null || navigationViewModel.nextStep.value != ShowSettings) {
+        _isShowingDialog.tryEmit(PreferenceViewModel.RenameDialog(fingerprintViewModel))
+      } else {
+        Log.d(TAG, "Ignoring onPrefClicked due to dialog showing ${_isShowingDialog.value}")
+      }
+    }
+  }
+
+  /** A request to delete a fingerprint */
+  fun deleteFingerprint(fp: FingerprintViewModel) {
+    viewModelScope.launch(backgroundDispatcher) {
+      if (fingerprintManagerInteractor.removeFingerprint(fp)) {
+        updateSettingsData()
+      }
+    }
+  }
+
+  /** A request to rename a fingerprint */
+  fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
+    viewModelScope.launch {
+      fingerprintManagerInteractor.renameFingerprint(fp, newName)
+      updateSettingsData()
+    }
+  }
+
+  private fun attemptingAuth() {
+    attemptsSoFar.update { it + 1 }
+  }
+
+  private suspend fun onAuthSuccess(success: FingerprintAuthAttemptViewModel.Success) {
+    _authSucceeded.emit(success)
+    attemptsSoFar.update { 0 }
+  }
+
+  private fun lockout(attemptViewModel: FingerprintAuthAttemptViewModel.Error) {
+    _isLockedOut.update { attemptViewModel }
+  }
+
+  /**
+   * This function is sort of a hack, it's used whenever we want to check for fingerprint state
+   * updates.
+   */
+  private suspend fun updateSettingsData() {
+    Log.d(TAG, "update settings data called")
+    val fingerprints = fingerprintManagerInteractor.enrolledFingerprints.last()
+    val canEnrollFingerprint =
+      fingerprintManagerInteractor.canEnrollFingerprints(fingerprints.size).last()
+    val maxFingerprints = fingerprintManagerInteractor.maxEnrollableFingerprints.last()
+    val hasSideFps = fingerprintManagerInteractor.hasSideFps()
+    val pressToAuthEnabled = fingerprintManagerInteractor.pressToAuthEnabled()
+    _fingerprintStateViewModel.update {
+      FingerprintStateViewModel(
+        fingerprints,
+        canEnrollFingerprint,
+        maxFingerprints,
+        hasSideFps,
+        pressToAuthEnabled
+      )
+    }
+  }
+
+  /** Used to indicate whether the consumer of the view model is ready for authentication. */
+  fun shouldAuthenticate(authenticate: Boolean) {
+    _consumerShouldAuthenticate.update { authenticate }
+  }
+
+  class FingerprintSettingsViewModelFactory(
+    private val userId: Int,
+    private val interactor: FingerprintManagerInteractor,
+    private val backgroundDispatcher: CoroutineDispatcher,
+    private val navigationViewModel: FingerprintSettingsNavigationViewModel,
+  ) : ViewModelProvider.Factory {
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : ViewModel> create(
+      modelClass: Class<T>,
+    ): T {
+
+      return FingerprintSettingsViewModel(
+        userId,
+        interactor,
+        backgroundDispatcher,
+        navigationViewModel,
+      )
+        as T
+    }
+  }
+}
+
+private inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+  flow: Flow<T1>,
+  flow2: Flow<T2>,
+  flow3: Flow<T3>,
+  flow4: Flow<T4>,
+  flow5: Flow<T5>,
+  flow6: Flow<T6>,
+  flow7: Flow<T7>,
+  crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+  return combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
+    @Suppress("UNCHECKED_CAST")
+    transform(
+      args[0] as T1,
+      args[1] as T2,
+      args[2] as T3,
+      args[3] as T4,
+      args[4] as T5,
+      args[5] as T6,
+      args[6] as T7,
+    )
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintViewModel.kt
new file mode 100644
index 0000000..1df0e34
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/FingerprintViewModel.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.viewmodel
+
+/** Represents the fingerprint data nad the relevant state. */
+data class FingerprintStateViewModel(
+  val fingerprintViewModels: List<FingerprintViewModel>,
+  val canEnroll: Boolean,
+  val maxFingerprints: Int,
+  val hasSideFps: Boolean,
+  val pressToAuth: Boolean,
+)
+
+data class FingerprintViewModel(
+  val name: String,
+  val fingerId: Int,
+  val deviceId: Long,
+)
+
+sealed class FingerprintAuthAttemptViewModel {
+  data class Success(
+    val fingerId: Int,
+  ) : FingerprintAuthAttemptViewModel()
+
+  data class Error(
+    val error: Int,
+    val message: String,
+  ) : FingerprintAuthAttemptViewModel()
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/NextStepViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/NextStepViewModel.kt
new file mode 100644
index 0000000..f9dbbff
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/NextStepViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.viewmodel
+
+/**
+ * A class to represent a high level step for FingerprintSettings. This is typically to perform an
+ * action like launching an activity.
+ */
+sealed class NextStepViewModel
+
+data class EnrollFirstFingerprint(
+  val userId: Int,
+  val gateKeeperPasswordHandle: Long?,
+  val challenge: Long?,
+  val challengeToken: ByteArray?,
+) : NextStepViewModel()
+
+data class EnrollAdditionalFingerprint(
+  val userId: Int,
+  val challengeToken: ByteArray?,
+) : NextStepViewModel()
+
+data class FinishSettings(val reason: String) : NextStepViewModel()
+
+data class FinishSettingsWithResult(val result: Int, val reason: String) : NextStepViewModel()
+
+object ShowSettings : NextStepViewModel()
+
+object LaunchedActivity : NextStepViewModel()
+
+data class LaunchConfirmDeviceCredential(val userId: Int) : NextStepViewModel()
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/PreferenceViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/PreferenceViewModel.kt
new file mode 100644
index 0000000..05764a2
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/viewmodel/PreferenceViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.ui.viewmodel
+
+/** Classed use to represent a Dialogs state. */
+sealed class PreferenceViewModel {
+  data class RenameDialog(
+    val fingerprintViewModel: FingerprintViewModel,
+  ) : PreferenceViewModel()
+
+  data class DeleteDialog(
+    val fingerprintViewModel: FingerprintViewModel,
+  ) : PreferenceViewModel()
+}
diff --git a/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java b/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java
index 93a2747..0690186 100644
--- a/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java
+++ b/src/com/android/settings/bluetooth/BlockingPrefWithSliceController.java
@@ -59,7 +59,7 @@
  * until {@link Slice} is fully loaded.
  */
 public class BlockingPrefWithSliceController extends BasePreferenceController implements
-        LifecycleObserver, OnStart, OnStop, Observer<Slice>, BasePreferenceController.UiBlocker{
+        LifecycleObserver, OnStart, OnStop, Observer<Slice>, BasePreferenceController.UiBlocker {
     private static final String TAG = "BlockingPrefWithSliceController";
 
     private static final String PREFIX_KEY = "slice_preference_item_";
@@ -225,7 +225,8 @@
             } else {
                 expectedActivityIntent = intentFromSliceAction;
             }
-            if (expectedActivityIntent != null) {
+            if (expectedActivityIntent != null && expectedActivityIntent.resolveActivity(
+                    mContext.getPackageManager()) != null) {
                 Log.d(TAG, "setIntent: ActivityIntent" + expectedActivityIntent);
                 // Since UI needs to support the Settings' 2 panel feature, the intent can't use the
                 // FLAG_ACTIVITY_NEW_TASK. The above intent may have the FLAG_ACTIVITY_NEW_TASK
@@ -234,6 +235,7 @@
                 preference.setIntent(expectedActivityIntent);
             } else {
                 Log.d(TAG, "setIntent: Intent is null");
+                preference.setSelectable(false);
             }
         }
 
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java
new file mode 100644
index 0000000..ba5f465
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeController.java
@@ -0,0 +1,180 @@
+/*
+ * 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.bluetooth;
+
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_OTHER;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceCategory;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.bluetooth.A2dpProfile;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LeAudioProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Controller responsible for the bluetooth audio device type selection
+ */
+public class BluetoothDetailsAudioDeviceTypeController extends BluetoothDetailsController
+        implements Preference.OnPreferenceChangeListener {
+    private static final String TAG = "BluetoothDetailsAudioDeviceTypeController";
+
+    private static final boolean DEBUG = false;
+
+    private static final String KEY_BT_AUDIO_DEVICE_TYPE_GROUP =
+            "bluetooth_audio_device_type_group";
+    private static final String KEY_BT_AUDIO_DEVICE_TYPE = "bluetooth_audio_device_type";
+
+    private final AudioManager mAudioManager;
+
+    private ListPreference mAudioDeviceTypePreference;
+
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    @VisibleForTesting
+    PreferenceCategory mProfilesContainer;
+
+    public BluetoothDetailsAudioDeviceTypeController(
+            Context context,
+            PreferenceFragmentCompat fragment,
+            LocalBluetoothManager manager,
+            CachedBluetoothDevice device,
+            Lifecycle lifecycle) {
+        super(context, fragment, device, lifecycle);
+        mAudioManager = context.getSystemService(AudioManager.class);
+        mProfileManager = manager.getProfileManager();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        // Available only for A2DP and BLE devices.
+        A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
+        boolean a2dpProfileEnabled = false;
+        if (a2dpProfile != null) {
+            a2dpProfileEnabled = a2dpProfile.isEnabled(mCachedDevice.getDevice());
+        }
+
+        LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile();
+        boolean leAudioProfileEnabled = false;
+        if (leAudioProfile != null) {
+            leAudioProfileEnabled = leAudioProfile.isEnabled(mCachedDevice.getDevice());
+        }
+
+        return a2dpProfileEnabled || leAudioProfileEnabled;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (preference instanceof ListPreference) {
+            final ListPreference pref = (ListPreference) preference;
+            final String key = pref.getKey();
+            if (key.equals(KEY_BT_AUDIO_DEVICE_TYPE)) {
+                if (newValue instanceof String) {
+                    final String value = (String) newValue;
+                    final int index = pref.findIndexOfValue(value);
+                    if (index >= 0) {
+                        pref.setSummary(pref.getEntries()[index]);
+                        mAudioManager.setBluetoothAudioDeviceCategory(mCachedDevice.getAddress(),
+                                mCachedDevice.getDevice().getType() == DEVICE_TYPE_LE,
+                                Integer.parseInt(value));
+                    }
+                }
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_BT_AUDIO_DEVICE_TYPE_GROUP;
+    }
+
+    @Override
+    protected void init(PreferenceScreen screen) {
+        mProfilesContainer = screen.findPreference(getPreferenceKey());
+        refresh();
+    }
+
+    @Override
+    protected void refresh() {
+        mAudioDeviceTypePreference = mProfilesContainer.findPreference(
+                KEY_BT_AUDIO_DEVICE_TYPE);
+        if (mAudioDeviceTypePreference == null) {
+            createAudioDeviceTypePreference(mProfilesContainer.getContext());
+            mProfilesContainer.addPreference(mAudioDeviceTypePreference);
+        }
+    }
+
+    @VisibleForTesting
+    void createAudioDeviceTypePreference(Context context) {
+        mAudioDeviceTypePreference = new ListPreference(context);
+        mAudioDeviceTypePreference.setKey(KEY_BT_AUDIO_DEVICE_TYPE);
+        mAudioDeviceTypePreference.setTitle(
+                mContext.getString(R.string.bluetooth_details_audio_device_types_title));
+        mAudioDeviceTypePreference.setEntries(new CharSequence[]{
+                mContext.getString(R.string.bluetooth_details_audio_device_type_unknown),
+                mContext.getString(R.string.bluetooth_details_audio_device_type_speaker),
+                mContext.getString(R.string.bluetooth_details_audio_device_type_headphones),
+                mContext.getString(R.string.bluetooth_details_audio_device_type_carkit),
+                mContext.getString(R.string.bluetooth_details_audio_device_type_other),
+        });
+        mAudioDeviceTypePreference.setEntryValues(new CharSequence[]{
+                Integer.toString(AUDIO_DEVICE_CATEGORY_UNKNOWN),
+                Integer.toString(AUDIO_DEVICE_CATEGORY_SPEAKER),
+                Integer.toString(AUDIO_DEVICE_CATEGORY_HEADPHONES),
+                Integer.toString(AUDIO_DEVICE_CATEGORY_CARKIT),
+                Integer.toString(AUDIO_DEVICE_CATEGORY_OTHER),
+        });
+
+        @AudioDeviceCategory final int deviceCategory =
+                mAudioManager.getBluetoothAudioDeviceCategory(mCachedDevice.getAddress(),
+                        mCachedDevice.getDevice().getType() == DEVICE_TYPE_LE);
+        if (DEBUG) {
+            Log.v(TAG, "getBluetoothAudioDeviceCategory() device: "
+                    + mCachedDevice.getDevice().getAnonymizedAddress()
+                    + ", has audio device category: " + deviceCategory);
+        }
+        mAudioDeviceTypePreference.setValue(Integer.toString(deviceCategory));
+
+        mAudioDeviceTypePreference.setSummary(mAudioDeviceTypePreference.getEntry());
+        mAudioDeviceTypePreference.setOnPreferenceChangeListener(this);
+    }
+
+    @VisibleForTesting
+    ListPreference getAudioDeviceTypePreference() {
+        return mAudioDeviceTypePreference;
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 3472e39..1fd09a3 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -69,7 +69,7 @@
     private static final String ENABLE_DUAL_MODE_AUDIO =
             "persist.bluetooth.enable_dual_mode_audio";
     private static final String CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT = "le_audio_enabled_by_default";
-    private static final boolean LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE = false;
+    private static final boolean LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE = true;
 
     private LocalBluetoothManager mManager;
     private LocalBluetoothProfileManager mProfileManager;
@@ -89,7 +89,7 @@
         mManager = manager;
         mProfileManager = mManager.getProfileManager();
         mCachedDevice = device;
-        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
         lifecycle.addObserver(this);
     }
 
@@ -324,11 +324,16 @@
             return;
         }
 
+        LocalBluetoothProfile asha = mProfileManager.getHearingAidProfile();
+
         for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) {
             Log.d(TAG,
                     "device:" + leAudioDevice.getDevice().getAnonymizedAddress()
                             + "disable LE profile");
             profile.setEnabled(leAudioDevice.getDevice(), false);
+            if (asha != null) {
+                asha.setEnabled(leAudioDevice.getDevice(), true);
+            }
         }
 
         if (!SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false)) {
@@ -354,12 +359,16 @@
             disableProfileBeforeUserEnablesLeAudio(mProfileManager.getA2dpProfile());
             disableProfileBeforeUserEnablesLeAudio(mProfileManager.getHeadsetProfile());
         }
+        LocalBluetoothProfile asha = mProfileManager.getHearingAidProfile();
 
         for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) {
             Log.d(TAG,
                     "device:" + leAudioDevice.getDevice().getAnonymizedAddress()
                             + "enable LE profile");
             profile.setEnabled(leAudioDevice.getDevice(), true);
+            if (asha != null) {
+                asha.setEnabled(leAudioDevice.getDevice(), false);
+            }
         }
     }
 
@@ -376,6 +385,12 @@
                             + profile.toString() + " profile is disabled. Do nothing.");
                 }
             }
+        } else {
+            if (profile == null) {
+                Log.w(TAG, "profile is null");
+            } else {
+                Log.w(TAG, profile.toString() + " is not in " + mProfileDeviceMap);
+            }
         }
     }
 
@@ -392,6 +407,12 @@
                             + profile.toString() + " profile is enabled. Do nothing.");
                 }
             }
+        } else {
+            if (profile == null) {
+                Log.w(TAG, "profile is null");
+            } else {
+                Log.w(TAG, profile.toString() + " is not in " + mProfileDeviceMap);
+            }
         }
     }
 
@@ -460,7 +481,7 @@
         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
             item.unregisterCallback(this);
         }
-        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
             item.registerCallback(this);
         }
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 99f3e31..c48494b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -300,6 +300,8 @@
                     lifecycle));
             controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
                     mCachedDevice, lifecycle));
+            controllers.add(new BluetoothDetailsAudioDeviceTypeController(context, this, mManager,
+                    mCachedDevice, lifecycle));
             controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
                     lifecycle));
             controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
index 7ee61ee..f2bc6fc 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -128,7 +128,7 @@
             if (device != null && mSelectedList.contains(device)) {
                 setResult(RESULT_OK);
                 finish();
-            } else if (mDevicePreferenceMap.containsKey(cachedDevice)) {
+            } else {
                 onDeviceDeleted(cachedDevice);
             }
         }
@@ -175,8 +175,6 @@
     public void updateContent(int bluetoothState) {
         switch (bluetoothState) {
             case BluetoothAdapter.STATE_ON:
-                mDevicePreferenceMap.clear();
-                clearPreferenceGroupCache();
                 mBluetoothAdapter.enable();
                 enableScanning();
                 break;
@@ -187,14 +185,6 @@
         }
     }
 
-    /**
-     * Clears all cached preferences in {@code preferenceGroup}.
-     */
-    private void clearPreferenceGroupCache() {
-        cacheRemoveAllPrefs(mAvailableDevicesCategory);
-        removeCachedPrefs(mAvailableDevicesCategory);
-    }
-
     @VisibleForTesting
     void showBluetoothTurnedOnToast() {
         Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 5256f3d..039080b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -35,6 +35,8 @@
 import android.widget.ImageView;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.AlertDialog;
 import androidx.preference.Preference;
@@ -52,6 +54,7 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * BluetoothDevicePreference is the preference type used to display each remote
@@ -79,7 +82,9 @@
     @VisibleForTesting
     BluetoothAdapter mBluetoothAdapter;
     private final boolean mShowDevicesWithoutNames;
-    private final long mCurrentTime;
+    @NonNull
+    private static final AtomicInteger sNextId = new AtomicInteger();
+    private final int mId;
     private final int mType;
 
     private AlertDialog mDisconnectDialog;
@@ -127,8 +132,9 @@
 
         mCachedDevice = cachedDevice;
         mCallback = new BluetoothDevicePreferenceCallback();
-        mCurrentTime = System.currentTimeMillis();
+        mId = sNextId.getAndIncrement();
         mType = type;
+        setVisible(false);
 
         onPreferenceAttributesChanged();
     }
@@ -229,35 +235,41 @@
 
     @SuppressWarnings("FutureReturnValueIgnored")
     void onPreferenceAttributesChanged() {
-        Pair<Drawable, String> pair = mCachedDevice.getDrawableWithDescription();
-        setIcon(pair.first);
-        contentDescription = pair.second;
-
-        /*
-         * The preference framework takes care of making sure the value has
-         * changed before proceeding. It will also call notifyChanged() if
-         * any preference info has changed from the previous value.
-         */
-        setTitle(mCachedDevice.getName());
         try {
             ThreadUtils.postOnBackgroundThread(() -> {
+                @Nullable String name = mCachedDevice.getName();
                 // Null check is done at the framework
-                ThreadUtils.postOnMainThread(() -> setSummary(getConnectionSummary()));
+                @Nullable String connectionSummary = getConnectionSummary();
+                @NonNull Pair<Drawable, String> pair = mCachedDevice.getDrawableWithDescription();
+                boolean isBusy = mCachedDevice.isBusy();
+                // Device is only visible in the UI if it has a valid name besides MAC address or
+                // when user allows showing devices without user-friendly name in developer settings
+                boolean isVisible =
+                        mShowDevicesWithoutNames || mCachedDevice.hasHumanReadableName();
+
+                ThreadUtils.postOnMainThread(() -> {
+                    /*
+                     * The preference framework takes care of making sure the value has
+                     * changed before proceeding. It will also call notifyChanged() if
+                     * any preference info has changed from the previous value.
+                     */
+                    setTitle(name);
+                    setSummary(connectionSummary);
+                    setIcon(pair.first);
+                    contentDescription = pair.second;
+                    // Used to gray out the item
+                    setEnabled(!isBusy);
+                    setVisible(isVisible);
+
+                    // This could affect ordering, so notify that
+                    if (mNeedNotifyHierarchyChanged) {
+                        notifyHierarchyChanged();
+                    }
+                });
             });
         } catch (RejectedExecutionException e) {
             Log.w(TAG, "Handler thread unavailable, skipping getConnectionSummary!");
         }
-        // Used to gray out the item
-        setEnabled(!mCachedDevice.isBusy());
-
-        // Device is only visible in the UI if it has a valid name besides MAC address or when user
-        // allows showing devices without user-friendly name in developer settings
-        setVisible(mShowDevicesWithoutNames || mCachedDevice.hasHumanReadableName());
-
-        // This could affect ordering, so notify that
-        if (mNeedNotifyHierarchyChanged) {
-            notifyHierarchyChanged();
-        }
     }
 
     @Override
@@ -311,7 +323,7 @@
                 return mCachedDevice
                         .compareTo(((BluetoothDevicePreference) another).mCachedDevice);
             case SortType.TYPE_FIFO:
-                return mCurrentTime > ((BluetoothDevicePreference) another).mCurrentTime ? 1 : -1;
+                return mId > ((BluetoothDevicePreference) another).mId ? 1 : -1;
             default:
                 return super.compareTo(another);
         }
diff --git a/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java b/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java
index 05bc179..f9d083d 100644
--- a/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java
@@ -125,6 +125,10 @@
                         Log.w(TAG, "onSourceAdded: mSelectedPreference == null!");
                         return;
                     }
+                    if (mLeBroadcastAssistant != null
+                            && mLeBroadcastAssistant.isSearchInProgress()) {
+                        mLeBroadcastAssistant.stopSearchingForSources();
+                    }
                     getActivity().runOnUiThread(() -> updateListCategoryFromBroadcastMetadata(
                             mSelectedPreference.getBluetoothLeBroadcastMetadata(), true));
                 }
@@ -232,6 +236,9 @@
     public void onStop() {
         super.onStop();
         if (mLeBroadcastAssistant != null) {
+            if (mLeBroadcastAssistant.isSearchInProgress()) {
+                mLeBroadcastAssistant.stopSearchingForSources();
+            }
             mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
         }
     }
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
index a78bf27..234d6d2 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
@@ -101,10 +101,8 @@
         if (bluetoothState == BluetoothAdapter.STATE_ON) {
             if (mInitialScanStarted) {
                 // Don't show bonded devices when screen turned back on
-                setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
-                addCachedDevices();
+                addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
             }
-            setFilter(BluetoothDeviceFilter.ALL_FILTER);
             updateFooterPreference(mFooterPreference);
             mAlwaysDiscoverable.start();
         }
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
deleted file mode 100644
index a4a9891..0000000
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2011 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.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.text.BidiFormatter;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceGroup;
-
-import com.android.settings.R;
-import com.android.settings.dashboard.RestrictedDashboardFragment;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Parent class for settings fragments that contain a list of Bluetooth
- * devices.
- *
- * @see DevicePickerFragment
- */
-// TODO: Refactor this fragment
-public abstract class DeviceListPreferenceFragment extends
-        RestrictedDashboardFragment implements BluetoothCallback {
-
-    private static final String TAG = "DeviceListPreferenceFragment";
-
-    private static final String KEY_BT_SCAN = "bt_scan";
-
-    // Copied from BluetoothDeviceNoNamePreferenceController.java
-    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
-            "persist.bluetooth.showdeviceswithoutnames";
-
-    private BluetoothDeviceFilter.Filter mFilter;
-    private List<ScanFilter> mLeScanFilters;
-    private ScanCallback mScanCallback;
-
-    @VisibleForTesting
-    protected boolean mScanEnabled;
-
-    protected BluetoothDevice mSelectedDevice;
-
-    protected BluetoothAdapter mBluetoothAdapter;
-    protected LocalBluetoothManager mLocalManager;
-    protected CachedBluetoothDeviceManager mCachedDeviceManager;
-
-    @VisibleForTesting
-    protected PreferenceGroup mDeviceListGroup;
-
-    protected final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
-            new HashMap<>();
-    protected final List<BluetoothDevice> mSelectedList = new ArrayList<>();
-
-    protected boolean mShowDevicesWithoutNames;
-
-    public DeviceListPreferenceFragment(String restrictedKey) {
-        super(restrictedKey);
-        mFilter = BluetoothDeviceFilter.ALL_FILTER;
-    }
-
-    protected final void setFilter(BluetoothDeviceFilter.Filter filter) {
-        mFilter = filter;
-    }
-
-    protected final void setFilter(int filterType) {
-        mFilter = BluetoothDeviceFilter.getFilter(filterType);
-    }
-
-    /**
-     * Sets the bluetooth device scanning filter with {@link ScanFilter}s. It will change to start
-     * {@link BluetoothLeScanner} which will scan BLE device only.
-     *
-     * @param leScanFilters list of settings to filter scan result
-     */
-    protected void setFilter(List<ScanFilter> leScanFilters) {
-        mFilter = null;
-        mLeScanFilters = leScanFilters;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mLocalManager = Utils.getLocalBtManager(getActivity());
-        if (mLocalManager == null) {
-            Log.e(TAG, "Bluetooth is not supported on this device");
-            return;
-        }
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        mCachedDeviceManager = mLocalManager.getCachedDeviceManager();
-        mShowDevicesWithoutNames = SystemProperties.getBoolean(
-                BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
-
-        initPreferencesFromPreferenceScreen();
-
-        mDeviceListGroup = (PreferenceCategory) findPreference(getDeviceListKey());
-    }
-
-    /** find and update preference that already existed in preference screen */
-    protected abstract void initPreferencesFromPreferenceScreen();
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (mLocalManager == null || isUiRestricted()) return;
-
-        mLocalManager.setForegroundActivity(getActivity());
-        mLocalManager.getEventManager().registerCallback(this);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (mLocalManager == null || isUiRestricted()) {
-            return;
-        }
-
-        removeAllDevices();
-        mLocalManager.setForegroundActivity(null);
-        mLocalManager.getEventManager().unregisterCallback(this);
-    }
-
-    void removeAllDevices() {
-        mDevicePreferenceMap.clear();
-        mDeviceListGroup.removeAll();
-    }
-
-    void addCachedDevices() {
-        Collection<CachedBluetoothDevice> cachedDevices =
-                mCachedDeviceManager.getCachedDevicesCopy();
-        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
-            onDeviceAdded(cachedDevice);
-        }
-    }
-
-    @Override
-    public boolean onPreferenceTreeClick(Preference preference) {
-        if (KEY_BT_SCAN.equals(preference.getKey())) {
-            startScanning();
-            return true;
-        }
-
-        if (preference instanceof BluetoothDevicePreference) {
-            BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
-            CachedBluetoothDevice device = btPreference.getCachedDevice();
-            mSelectedDevice = device.getDevice();
-            mSelectedList.add(mSelectedDevice);
-            onDevicePreferenceClick(btPreference);
-            return true;
-        }
-
-        return super.onPreferenceTreeClick(preference);
-    }
-
-    protected void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
-        btPreference.onClicked();
-    }
-
-    @Override
-    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-        if (mDevicePreferenceMap.get(cachedDevice) != null) {
-            return;
-        }
-
-        // Prevent updates while the list shows one of the state messages
-        if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) {
-            return;
-        }
-
-        if (mFilter != null && mFilter.matches(cachedDevice.getDevice())) {
-            createDevicePreference(cachedDevice);
-        }
-    }
-
-    void createDevicePreference(CachedBluetoothDevice cachedDevice) {
-        if (mDeviceListGroup == null) {
-            Log.w(TAG, "Trying to create a device preference before the list group/category "
-                    + "exists!");
-            return;
-        }
-
-        String key = cachedDevice.getDevice().getAddress();
-        BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key);
-
-        if (preference == null) {
-            preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice,
-                    mShowDevicesWithoutNames, BluetoothDevicePreference.SortType.TYPE_FIFO);
-            preference.setKey(key);
-            //Set hideSecondTarget is true if it's bonded device.
-            preference.hideSecondTarget(true);
-            mDeviceListGroup.addPreference(preference);
-        }
-
-        initDevicePreference(preference);
-        mDevicePreferenceMap.put(cachedDevice, preference);
-    }
-
-    protected void initDevicePreference(BluetoothDevicePreference preference) {
-        // Does nothing by default
-    }
-
-    @VisibleForTesting
-    void updateFooterPreference(Preference myDevicePreference) {
-        final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
-
-        myDevicePreference.setTitle(getString(
-                R.string.bluetooth_footer_mac_message,
-                bidiFormatter.unicodeWrap(mBluetoothAdapter.getAddress())));
-    }
-
-    @Override
-    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
-        if (preference != null) {
-            mDeviceListGroup.removePreference(preference);
-        }
-    }
-
-    @VisibleForTesting
-    protected void enableScanning() {
-        // BluetoothAdapter already handles repeated scan requests
-        if (!mScanEnabled) {
-            startScanning();
-            mScanEnabled = true;
-        }
-    }
-
-    @VisibleForTesting
-    protected void disableScanning() {
-        if (mScanEnabled) {
-            stopScanning();
-            mScanEnabled = false;
-        }
-    }
-
-    @Override
-    public void onScanningStateChanged(boolean started) {
-        if (!started && mScanEnabled) {
-            startScanning();
-        }
-    }
-
-    /**
-     * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices
-     */
-    public abstract String getDeviceListKey();
-
-    public boolean shouldShowDevicesWithoutNames() {
-        return mShowDevicesWithoutNames;
-    }
-
-    @VisibleForTesting
-    void startScanning() {
-        if (mFilter != null) {
-            startClassicScanning();
-        } else if (mLeScanFilters != null) {
-            startLeScanning();
-        }
-
-    }
-
-    @VisibleForTesting
-    void stopScanning() {
-        if (mFilter != null) {
-            stopClassicScanning();
-        } else if (mLeScanFilters != null) {
-            stopLeScanning();
-        }
-    }
-
-    private void startClassicScanning() {
-        if (!mBluetoothAdapter.isDiscovering()) {
-            mBluetoothAdapter.startDiscovery();
-        }
-    }
-
-    private void stopClassicScanning() {
-        if (mBluetoothAdapter.isDiscovering()) {
-            mBluetoothAdapter.cancelDiscovery();
-        }
-    }
-
-    private void startLeScanning() {
-        final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
-        final ScanSettings settings = new ScanSettings.Builder()
-                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
-                .build();
-        mScanCallback = new ScanCallback() {
-            @Override
-            public void onScanResult(int callbackType, ScanResult result) {
-                final BluetoothDevice device = result.getDevice();
-                CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
-                if (cachedDevice == null) {
-                    cachedDevice = mCachedDeviceManager.addDevice(device);
-                }
-                // Only add device preference when it's not found in the map and there's no other
-                // state message showing in the list
-                if (mDevicePreferenceMap.get(cachedDevice) == null
-                        && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
-                    createDevicePreference(cachedDevice);
-                }
-            }
-
-            @Override
-            public void onScanFailed(int errorCode) {
-                Log.w(TAG, "BLE Scan failed with error code " + errorCode);
-            }
-        };
-        scanner.startScan(mLeScanFilters, settings, mScanCallback);
-    }
-
-    private void stopLeScanning() {
-        final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
-        if (scanner != null) {
-            scanner.stopScan(mScanCallback);
-        }
-    }
-}
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
new file mode 100644
index 0000000..f18ae46
--- /dev/null
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.le.BluetoothLeScanner
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanFilter
+import android.bluetooth.le.ScanResult
+import android.bluetooth.le.ScanSettings
+import android.os.Bundle
+import android.os.SystemProperties
+import android.text.BidiFormatter
+import android.util.Log
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceGroup
+import com.android.settings.R
+import com.android.settings.dashboard.RestrictedDashboardFragment
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Parent class for settings fragments that contain a list of Bluetooth devices.
+ *
+ * @see DevicePickerFragment
+ *
+ * TODO: Refactor this fragment
+ */
+abstract class DeviceListPreferenceFragment(restrictedKey: String?) :
+    RestrictedDashboardFragment(restrictedKey), BluetoothCallback {
+
+    private var filter: BluetoothDeviceFilter.Filter? = BluetoothDeviceFilter.ALL_FILTER
+    private var leScanFilters: List<ScanFilter>? = null
+
+    @JvmField
+    @VisibleForTesting
+    var mScanEnabled = false
+
+    @JvmField
+    var mSelectedDevice: BluetoothDevice? = null
+
+    @JvmField
+    var mBluetoothAdapter: BluetoothAdapter? = null
+
+    @JvmField
+    var mLocalManager: LocalBluetoothManager? = null
+
+    @JvmField
+    var mCachedDeviceManager: CachedBluetoothDeviceManager? = null
+
+    @JvmField
+    @VisibleForTesting
+    var mDeviceListGroup: PreferenceGroup? = null
+
+    @VisibleForTesting
+    val devicePreferenceMap =
+        ConcurrentHashMap<CachedBluetoothDevice, BluetoothDevicePreference>()
+
+    @JvmField
+    val mSelectedList: MutableList<BluetoothDevice> = ArrayList()
+
+    @VisibleForTesting
+    var lifecycleScope: CoroutineScope? = null
+
+    private var showDevicesWithoutNames = false
+
+    protected fun setFilter(filterType: Int) {
+        filter = BluetoothDeviceFilter.getFilter(filterType)
+    }
+
+    /**
+     * Sets the bluetooth device scanning filter with [ScanFilter]s. It will change to start
+     * [BluetoothLeScanner] which will scan BLE device only.
+     *
+     * @param leScanFilters list of settings to filter scan result
+     */
+    fun setFilter(leScanFilters: List<ScanFilter>?) {
+        filter = null
+        this.leScanFilters = leScanFilters
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        mLocalManager = Utils.getLocalBtManager(activity)
+        if (mLocalManager == null) {
+            Log.e(TAG, "Bluetooth is not supported on this device")
+            return
+        }
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
+        mCachedDeviceManager = mLocalManager!!.cachedDeviceManager
+        showDevicesWithoutNames = SystemProperties.getBoolean(
+            BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false
+        )
+        initPreferencesFromPreferenceScreen()
+        mDeviceListGroup = findPreference<Preference>(deviceListKey) as PreferenceCategory
+    }
+
+    /** find and update preference that already existed in preference screen  */
+    protected abstract fun initPreferencesFromPreferenceScreen()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        lifecycleScope = viewLifecycleOwner.lifecycleScope
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (mLocalManager == null || isUiRestricted) return
+        mLocalManager!!.foregroundActivity = activity
+        mLocalManager!!.eventManager.registerCallback(this)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        if (mLocalManager == null || isUiRestricted) {
+            return
+        }
+        removeAllDevices()
+        mLocalManager!!.foregroundActivity = null
+        mLocalManager!!.eventManager.unregisterCallback(this)
+    }
+
+    fun removeAllDevices() {
+        devicePreferenceMap.clear()
+        mDeviceListGroup!!.removeAll()
+    }
+
+    @JvmOverloads
+    fun addCachedDevices(filterForCachedDevices: BluetoothDeviceFilter.Filter? = null) {
+        lifecycleScope?.launch {
+            withContext(Dispatchers.Default) {
+                mCachedDeviceManager!!.cachedDevicesCopy
+                    .filter {
+                        filterForCachedDevices == null || filterForCachedDevices.matches(it.device)
+                    }
+                    .forEach(::onDeviceAdded)
+            }
+        }
+    }
+
+    override fun onPreferenceTreeClick(preference: Preference): Boolean {
+        if (KEY_BT_SCAN == preference.key) {
+            startScanning()
+            return true
+        }
+        if (preference is BluetoothDevicePreference) {
+            val device = preference.cachedDevice.device
+            mSelectedDevice = device
+            mSelectedList.add(device)
+            onDevicePreferenceClick(preference)
+            return true
+        }
+        return super.onPreferenceTreeClick(preference)
+    }
+
+    protected open fun onDevicePreferenceClick(btPreference: BluetoothDevicePreference) {
+        btPreference.onClicked()
+    }
+
+    override fun onDeviceAdded(cachedDevice: CachedBluetoothDevice) {
+        lifecycleScope?.launch {
+            addDevice(cachedDevice)
+        }
+    }
+
+    private suspend fun addDevice(cachedDevice: CachedBluetoothDevice) =
+        withContext(Dispatchers.Default) {
+            // TODO(b/289189853): Replace checking if `filter` is null or not to decide which type
+            // of Bluetooth scanning method will be used
+            val filterMatched = filter == null || filter!!.matches(cachedDevice.device) == true
+            // Prevent updates while the list shows one of the state messages
+            if (mBluetoothAdapter!!.state == BluetoothAdapter.STATE_ON && filterMatched) {
+                createDevicePreference(cachedDevice)
+            }
+        }
+
+    private suspend fun createDevicePreference(cachedDevice: CachedBluetoothDevice) {
+        if (mDeviceListGroup == null) {
+            Log.w(
+                TAG,
+                "Trying to create a device preference before the list group/category exists!",
+            )
+            return
+        }
+        // Only add device preference when it's not found in the map and there's no other state
+        // message showing in the list
+        val preference = devicePreferenceMap.computeIfAbsent(cachedDevice) {
+            BluetoothDevicePreference(
+                prefContext,
+                cachedDevice,
+                showDevicesWithoutNames,
+                BluetoothDevicePreference.SortType.TYPE_FIFO,
+            ).apply {
+                key = cachedDevice.device.address
+                //Set hideSecondTarget is true if it's bonded device.
+                hideSecondTarget(true)
+            }
+        }
+        withContext(Dispatchers.Main) {
+            mDeviceListGroup!!.addPreference(preference)
+            initDevicePreference(preference)
+        }
+    }
+
+    protected open fun initDevicePreference(preference: BluetoothDevicePreference?) {
+        // Does nothing by default
+    }
+
+    @VisibleForTesting
+    fun updateFooterPreference(myDevicePreference: Preference) {
+        val bidiFormatter = BidiFormatter.getInstance()
+        myDevicePreference.title = getString(
+            R.string.bluetooth_footer_mac_message,
+            bidiFormatter.unicodeWrap(mBluetoothAdapter!!.address)
+        )
+    }
+
+    override fun onDeviceDeleted(cachedDevice: CachedBluetoothDevice) {
+        devicePreferenceMap.remove(cachedDevice)?.let {
+            mDeviceListGroup!!.removePreference(it)
+        }
+    }
+
+    @VisibleForTesting
+    open fun enableScanning() {
+        // BluetoothAdapter already handles repeated scan requests
+        if (!mScanEnabled) {
+            startScanning()
+            mScanEnabled = true
+        }
+    }
+
+    @VisibleForTesting
+    fun disableScanning() {
+        if (mScanEnabled) {
+            stopScanning()
+            mScanEnabled = false
+        }
+    }
+
+    override fun onScanningStateChanged(started: Boolean) {
+        if (!started && mScanEnabled) {
+            startScanning()
+        }
+    }
+
+    /**
+     * Return the key of the [PreferenceGroup] that contains the bluetooth devices
+     */
+    abstract val deviceListKey: String
+
+    @VisibleForTesting
+    open fun startScanning() {
+        if (filter != null) {
+            startClassicScanning()
+        } else if (leScanFilters != null) {
+            startLeScanning()
+        }
+    }
+
+    @VisibleForTesting
+    open fun stopScanning() {
+        if (filter != null) {
+            stopClassicScanning()
+        } else if (leScanFilters != null) {
+            stopLeScanning()
+        }
+    }
+
+    private fun startClassicScanning() {
+        if (!mBluetoothAdapter!!.isDiscovering) {
+            mBluetoothAdapter!!.startDiscovery()
+        }
+    }
+
+    private fun stopClassicScanning() {
+        if (mBluetoothAdapter!!.isDiscovering) {
+            mBluetoothAdapter!!.cancelDiscovery()
+        }
+    }
+
+    private val leScanCallback = object : ScanCallback() {
+        override fun onScanResult(callbackType: Int, result: ScanResult) {
+            handleLeScanResult(result)
+        }
+
+        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
+            for (result in results.orEmpty()) {
+                handleLeScanResult(result)
+            }
+        }
+
+        override fun onScanFailed(errorCode: Int) {
+            Log.w(TAG, "BLE Scan failed with error code $errorCode")
+        }
+    }
+
+    private fun startLeScanning() {
+        val scanner = mBluetoothAdapter!!.bluetoothLeScanner
+        val settings = ScanSettings.Builder()
+            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+            .build()
+        scanner.startScan(leScanFilters, settings, leScanCallback)
+    }
+
+    private fun stopLeScanning() {
+        val scanner = mBluetoothAdapter!!.bluetoothLeScanner
+        scanner?.stopScan(leScanCallback)
+    }
+
+    private fun handleLeScanResult(result: ScanResult) {
+        lifecycleScope?.launch {
+            withContext(Dispatchers.Default) {
+                val device = result.device
+                val cachedDevice = mCachedDeviceManager!!.findDevice(device)
+                    ?: mCachedDeviceManager!!.addDevice(device, leScanFilters)
+                addDevice(cachedDevice)
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "DeviceListPreferenceFragment"
+        private const val KEY_BT_SCAN = "bt_scan"
+
+        // Copied from BluetoothDeviceNoNamePreferenceController.java
+        private const val BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
+            "persist.bluetooth.showdeviceswithoutnames"
+    }
+}
diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
index e30bbfb..f72494f 100644
--- a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java
@@ -88,6 +88,7 @@
 
     @VisibleForTesting
     LayoutPreference mLayoutPreference;
+    LocalBluetoothManager mManager;
     private CachedBluetoothDevice mCachedDevice;
     private List<CachedBluetoothDevice> mAllOfCachedDevices;
     @VisibleForTesting
@@ -152,8 +153,9 @@
     public void init(CachedBluetoothDevice cachedBluetoothDevice,
             LocalBluetoothManager bluetoothManager) {
         mCachedDevice = cachedBluetoothDevice;
+        mManager = bluetoothManager;
         mProfileManager = bluetoothManager.getProfileManager();
-        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
     }
 
     @VisibleForTesting
@@ -300,7 +302,7 @@
         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
             item.unregisterCallback(this);
         }
-        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mContext, mCachedDevice);
+        mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice);
         for (CachedBluetoothDevice item : mAllOfCachedDevices) {
             item.registerCallback(this);
         }
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index 79a2de0..f1d6b20 100644
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -235,7 +235,8 @@
      * @param cachedBluetoothDevice The main cachedBluetoothDevice.
      * @return all cachedBluetoothDevices with the same groupId.
      */
-    public static List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices(Context context,
+    public static List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices(
+            LocalBluetoothManager localBtMgr,
             CachedBluetoothDevice cachedBluetoothDevice) {
         List<CachedBluetoothDevice> cachedBluetoothDevices = new ArrayList<>();
         if (cachedBluetoothDevice == null) {
@@ -248,7 +249,6 @@
             return cachedBluetoothDevices;
         }
 
-        final LocalBluetoothManager localBtMgr = Utils.getLocalBtManager(context);
         if (localBtMgr == null) {
             Log.e(TAG, "getAllOfCachedBluetoothDevices: no LocalBluetoothManager");
             return cachedBluetoothDevices;
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java
index c93a1c6..985c8b7 100644
--- a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java
+++ b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java
@@ -16,12 +16,17 @@
 
 package com.android.settings.connecteddevice.stylus;
 
+import android.app.Dialog;
 import android.app.role.RoleManager;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.text.TextUtils;
@@ -38,6 +43,9 @@
 import androidx.preference.SwitchPreference;
 
 import com.android.settings.R;
+import com.android.settings.dashboard.profileselector.ProfileSelectDialog;
+import com.android.settings.dashboard.profileselector.UserAdapter;
+import com.android.settingslib.PrimarySwitchPreference;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -45,13 +53,15 @@
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnResume;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * This class adds stylus preferences.
  */
 public class StylusDevicesController extends AbstractPreferenceController implements
-        Preference.OnPreferenceClickListener, LifecycleObserver, OnResume {
+        Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener,
+        LifecycleObserver, OnResume {
 
     @VisibleForTesting
     static final String KEY_STYLUS = "device_stylus";
@@ -73,6 +83,9 @@
     @VisibleForTesting
     PreferenceCategory mPreferencesContainer;
 
+    @VisibleForTesting
+    Dialog mDialog;
+
     public StylusDevicesController(Context context, InputDevice inputDevice,
             CachedBluetoothDevice cachedBluetoothDevice, Lifecycle lifecycle) {
         super(context);
@@ -100,8 +113,8 @@
         pref.setOnPreferenceClickListener(this);
         pref.setEnabled(true);
 
-        List<String> roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES,
-                mContext.getUser());
+        UserHandle user = getDefaultNoteTaskProfile();
+        List<String> roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user);
         if (roleHolders.isEmpty()) {
             pref.setSummary(R.string.default_app_none);
             return pref;
@@ -113,19 +126,29 @@
         try {
             ApplicationInfo ai = pm.getApplicationInfo(packageName,
                     PackageManager.ApplicationInfoFlags.of(0));
-            appName = ai == null ? packageName : pm.getApplicationLabel(ai).toString();
+            appName = ai == null ? "" : pm.getApplicationLabel(ai).toString();
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Notes role package not found.");
         }
-        pref.setSummary(appName);
+
+        if (mContext.getSystemService(UserManager.class).isManagedProfile(user.getIdentifier())) {
+            pref.setSummary(
+                    mContext.getString(R.string.stylus_default_notes_summary_work, appName));
+        } else {
+            pref.setSummary(appName);
+        }
         return pref;
     }
 
-    private SwitchPreference createOrUpdateHandwritingPreference(SwitchPreference preference) {
-        SwitchPreference pref = preference == null ? new SwitchPreference(mContext) : preference;
+    private PrimarySwitchPreference createOrUpdateHandwritingPreference(
+            PrimarySwitchPreference preference) {
+        PrimarySwitchPreference pref = preference == null ? new PrimarySwitchPreference(mContext)
+                : preference;
         pref.setKey(KEY_HANDWRITING);
         pref.setTitle(mContext.getString(R.string.stylus_textfield_handwriting));
         pref.setIcon(R.drawable.ic_text_fields_alt);
+        // Using a two-target preference, clicking will send an intent and change will toggle.
+        pref.setOnPreferenceChangeListener(this);
         pref.setOnPreferenceClickListener(this);
         pref.setChecked(Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.STYLUS_HANDWRITING_ENABLED,
@@ -148,30 +171,28 @@
     @Override
     public boolean onPreferenceClick(Preference preference) {
         String key = preference.getKey();
-
         switch (key) {
             case KEY_DEFAULT_NOTES:
                 PackageManager pm = mContext.getPackageManager();
                 String packageName = pm.getPermissionControllerPackageName();
                 Intent intent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP).setPackage(
                         packageName).putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_NOTES);
-                mContext.startActivity(intent);
+
+                List<UserHandle> users = getUserAndManagedProfiles();
+                if (users.size() <= 1) {
+                    mContext.startActivity(intent);
+                } else {
+                    createAndShowProfileSelectDialog(intent, users);
+                }
                 break;
             case KEY_HANDWRITING:
-                Settings.Secure.putInt(mContext.getContentResolver(),
-                        Settings.Secure.STYLUS_HANDWRITING_ENABLED,
-                        ((SwitchPreference) preference).isChecked() ? 1 : 0);
-
-                if (((SwitchPreference) preference).isChecked()) {
-                    InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-                    InputMethodInfo inputMethod = imm.getCurrentInputMethodInfo();
-                    if (inputMethod == null) break;
-
-                    Intent handwritingIntent =
-                            inputMethod.createStylusHandwritingSettingsActivityIntent();
-                    if (handwritingIntent != null) {
-                        mContext.startActivity(handwritingIntent);
-                    }
+                InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+                InputMethodInfo inputMethod = imm.getCurrentInputMethodInfo();
+                if (inputMethod == null) break;
+                Intent handwritingIntent =
+                        inputMethod.createStylusHandwritingSettingsActivityIntent();
+                if (handwritingIntent != null) {
+                    mContext.startActivity(handwritingIntent);
                 }
                 break;
             case KEY_IGNORE_BUTTON:
@@ -184,6 +205,19 @@
     }
 
     @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        String key = preference.getKey();
+        switch (key) {
+            case KEY_HANDWRITING:
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                        Settings.Secure.STYLUS_HANDWRITING_ENABLED,
+                        (boolean) newValue ? 1 : 0);
+                break;
+        }
+        return true;
+    }
+
+    @Override
     public final void displayPreference(PreferenceScreen screen) {
         mPreferencesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey());
         super.displayPreference(screen);
@@ -210,7 +244,7 @@
             mPreferencesContainer.addPreference(notesPref);
         }
 
-        SwitchPreference currHandwritingPref = mPreferencesContainer.findPreference(
+        PrimarySwitchPreference currHandwritingPref = mPreferencesContainer.findPreference(
                 KEY_HANDWRITING);
         Preference handwritingPref = createOrUpdateHandwritingPreference(currHandwritingPref);
         if (currHandwritingPref == null) {
@@ -229,6 +263,56 @@
         return inputMethod != null && inputMethod.supportsStylusHandwriting();
     }
 
+    private List<UserHandle> getUserAndManagedProfiles() {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        final List<UserHandle> userManagedProfiles = new ArrayList<>();
+        // Add the current user, then add all the associated managed profiles.
+        final UserHandle currentUser = Process.myUserHandle();
+        userManagedProfiles.add(currentUser);
+
+        final List<UserInfo> userInfos = um.getUsers();
+        for (UserInfo info : userInfos) {
+            int userId = info.id;
+            if (um.isManagedProfile(userId)
+                    && um.getProfileParent(userId).id == currentUser.getIdentifier()) {
+                userManagedProfiles.add(UserHandle.of(userId));
+            }
+        }
+        return userManagedProfiles;
+    }
+
+    private UserHandle getDefaultNoteTaskProfile() {
+        final int userId = Secure.getInt(
+                mContext.getContentResolver(),
+                Secure.DEFAULT_NOTE_TASK_PROFILE,
+                UserHandle.myUserId());
+        return UserHandle.of(userId);
+    }
+
+    @VisibleForTesting
+    UserAdapter.OnClickListener createProfileDialogClickCallback(
+            Intent intent, List<UserHandle> users) {
+        // TODO(b/281659827): improve UX flow for when activity is cancelled
+        return (int position) -> {
+            intent.putExtra(Intent.EXTRA_USER, users.get(position));
+
+            Secure.putInt(mContext.getContentResolver(),
+                    Secure.DEFAULT_NOTE_TASK_PROFILE,
+                    users.get(position).getIdentifier());
+            mContext.startActivity(intent);
+
+            mDialog.dismiss();
+        };
+    }
+
+    private void createAndShowProfileSelectDialog(Intent intent, List<UserHandle> users) {
+        mDialog = ProfileSelectDialog.createDialog(
+                mContext,
+                users,
+                createProfileDialogClickCallback(intent, users));
+        mDialog.show();
+    }
+
     /**
      * Identifies whether a device is a stylus using the associated {@link InputDevice} or
      * {@link CachedBluetoothDevice}.
@@ -255,5 +339,4 @@
 
         return false;
     }
-
 }
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusFeatureProvider.java b/src/com/android/settings/connecteddevice/stylus/StylusFeatureProvider.java
new file mode 100644
index 0000000..7ca35d8
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/StylusFeatureProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+
+import androidx.preference.Preference;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/** FeatureProvider for USB settings */
+public interface StylusFeatureProvider {
+
+    /**
+     * Returns whether the current attached USB device allows firmware updates.
+     *
+     * @param usbDevice The USB device to check
+     */
+    boolean isUsbFirmwareUpdateEnabled(UsbDevice usbDevice);
+
+    /**
+     * Returns a list of preferences for the connected USB device if exists. If not, returns
+     * null. If an update is not available but firmware update feature is enabled for the device,
+     * the list will contain only the preference showing the current firmware version.
+     *
+     * @param context The context
+     * @param usbDevice The USB device for which to generate preferences.
+     */
+    @Nullable
+    List<Preference> getUsbFirmwareUpdatePreferences(Context context, UsbDevice usbDevice);
+}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusFeatureProviderImpl.java b/src/com/android/settings/connecteddevice/stylus/StylusFeatureProviderImpl.java
new file mode 100644
index 0000000..be5ae40
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/StylusFeatureProviderImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+
+import androidx.preference.Preference;
+
+import java.util.List;
+
+/** Default implementation for StylusFeatureProvider */
+public class StylusFeatureProviderImpl implements StylusFeatureProvider {
+    @Override
+    public boolean isUsbFirmwareUpdateEnabled(UsbDevice usbDevice) {
+        return false;
+    }
+
+    @Override
+    public List<Preference> getUsbFirmwareUpdatePreferences(Context context, UsbDevice usbDevice) {
+        return null;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusUsbFirmwareController.java b/src/com/android/settings/connecteddevice/stylus/StylusUsbFirmwareController.java
new file mode 100644
index 0000000..9c567a4
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/StylusUsbFirmwareController.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Preference controller for stylus firmware updates via USB */
+public class StylusUsbFirmwareController extends BasePreferenceController
+        implements LifecycleObserver, OnStart, OnStop {
+    private static final String TAG = StylusUsbFirmwareController.class.getSimpleName();
+    @Nullable
+    private UsbDevice mStylusUsbDevice;
+    private final UsbStylusBroadcastReceiver mUsbStylusBroadcastReceiver;
+
+    private PreferenceScreen mPreferenceScreen;
+    private PreferenceCategory mPreference;
+
+    @VisibleForTesting
+    UsbStylusBroadcastReceiver.UsbStylusConnectionListener mUsbConnectionListener =
+            (stylusUsbDevice, attached) -> {
+                refresh();
+            };
+
+    public StylusUsbFirmwareController(Context context, String key) {
+        super(context, key);
+        mUsbStylusBroadcastReceiver = new UsbStylusBroadcastReceiver(context,
+                mUsbConnectionListener);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mPreferenceScreen = screen;
+        refresh();
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        // always available, preferences will be added or
+        // removed according to the connected usb device
+        return AVAILABLE;
+    }
+
+    private void refresh() {
+        if (mPreferenceScreen == null) return;
+
+        UsbDevice device = getStylusUsbDevice();
+        if (device == mStylusUsbDevice) {
+            return;
+        }
+        mStylusUsbDevice = device;
+        mPreference = mPreferenceScreen.findPreference(getPreferenceKey());
+        if (mPreference != null) {
+            mPreferenceScreen.removePreference(mPreference);
+        }
+        if (hasUsbStylusFirmwareUpdateFeature(mStylusUsbDevice)) {
+            StylusFeatureProvider featureProvider = FeatureFactory.getFactory(
+                    mContext).getStylusFeatureProvider();
+            List<Preference> preferences =
+                    featureProvider.getUsbFirmwareUpdatePreferences(mContext, mStylusUsbDevice);
+
+            if (preferences != null) {
+                mPreference = new PreferenceCategory(mContext);
+                mPreference.setKey(getPreferenceKey());
+                mPreferenceScreen.addPreference(mPreference);
+
+                for (Preference preference : preferences) {
+                    mPreference.addPreference(preference);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        mUsbStylusBroadcastReceiver.register();
+    }
+
+    @Override
+    public void onStop() {
+        mUsbStylusBroadcastReceiver.unregister();
+    }
+
+    private UsbDevice getStylusUsbDevice() {
+        UsbManager usbManager = mContext.getSystemService(UsbManager.class);
+
+        if (usbManager == null) {
+            return null;
+        }
+
+        List<UsbDevice> devices = new ArrayList<>(usbManager.getDeviceList().values());
+        if (devices.isEmpty()) {
+            return null;
+        }
+
+        UsbDevice usbDevice = devices.get(0);
+        if (hasUsbStylusFirmwareUpdateFeature(usbDevice)) {
+            return usbDevice;
+        }
+        return null;
+    }
+
+    static boolean hasUsbStylusFirmwareUpdateFeature(UsbDevice usbDevice) {
+        if (usbDevice == null) return false;
+
+        StylusFeatureProvider featureProvider = FeatureFactory.getFactory(
+                FeatureFactory.getAppContext()).getStylusFeatureProvider();
+
+        return featureProvider.isUsbFirmwareUpdateEnabled(usbDevice);
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java b/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java
index 5e68a53..ea9781e 100644
--- a/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java
+++ b/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java
@@ -54,7 +54,6 @@
         }
     }
 
-
     @Override
     public int getMetricsCategory() {
         return SettingsEnums.USI_DEVICE_DETAILS;
diff --git a/src/com/android/settings/connecteddevice/stylus/UsbStylusBroadcastReceiver.java b/src/com/android/settings/connecteddevice/stylus/UsbStylusBroadcastReceiver.java
new file mode 100644
index 0000000..41d88d2
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/UsbStylusBroadcastReceiver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+
+/** Broadcast receiver for styluses connected via USB */
+public class UsbStylusBroadcastReceiver extends BroadcastReceiver {
+    private Context mContext;
+    private UsbStylusConnectionListener mUsbConnectionListener;
+    private boolean mListeningToUsbEvents;
+
+    public UsbStylusBroadcastReceiver(Context context,
+            UsbStylusConnectionListener usbConnectionListener) {
+        mContext = context;
+        mUsbConnectionListener = usbConnectionListener;
+    }
+
+    /** Registers the receiver. */
+    public void register() {
+        if (!mListeningToUsbEvents) {
+            final IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+            intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+            intentFilter.addAction(UsbManager.ACTION_USB_STATE);
+            final Intent intent = mContext.registerReceiver(this, intentFilter);
+            if (intent != null) {
+                onReceive(mContext, intent);
+            }
+            mListeningToUsbEvents = true;
+        }
+    }
+
+    /** Unregisters the receiver. */
+    public void unregister() {
+        if (mListeningToUsbEvents) {
+            mContext.unregisterReceiver(this);
+            mListeningToUsbEvents = false;
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice.class);
+        if (StylusUsbFirmwareController.hasUsbStylusFirmwareUpdateFeature(usbDevice)) {
+            mUsbConnectionListener.onUsbStylusConnectionChanged(usbDevice,
+                    intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED));
+        }
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when stylus usb connection is changed.
+     */
+    interface UsbStylusConnectionListener {
+        void onUsbStylusConnectionChanged(UsbDevice device, boolean connected);
+    }
+}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 149d1f4..3100706 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -72,6 +72,7 @@
 import com.android.settings.biometrics.combination.CombinedBiometricSettings;
 import com.android.settings.biometrics.face.FaceSettings;
 import com.android.settings.biometrics.fingerprint.FingerprintSettings;
+import com.android.settings.biometrics.fingerprint2.ui.fragment.FingerprintSettingsV2Fragment;
 import com.android.settings.bluetooth.BluetoothBroadcastDialog;
 import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
 import com.android.settings.bluetooth.BluetoothFindBroadcastsFragment;
@@ -94,6 +95,7 @@
 import com.android.settings.deviceinfo.PublicVolumeSettings;
 import com.android.settings.deviceinfo.StorageDashboardFragment;
 import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment;
+import com.android.settings.deviceinfo.batteryinfo.BatteryInfoFragment;
 import com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings;
 import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
 import com.android.settings.display.AutoBrightnessSettings;
@@ -265,6 +267,7 @@
             AssistGestureSettings.class.getName(),
             FaceSettings.class.getName(),
             FingerprintSettings.FingerprintSettingsFragment.class.getName(),
+            FingerprintSettingsV2Fragment.class.getName(),
             CombinedBiometricSettings.class.getName(),
             CombinedBiometricProfileSettings.class.getName(),
             SwipeToNotificationSettings.class.getName(),
@@ -371,7 +374,8 @@
             NfcAndPaymentFragment.class.getName(),
             ColorAndMotionFragment.class.getName(),
             LongBackgroundTasksDetails.class.getName(),
-            RegionalPreferencesEntriesFragment.class.getName()
+            RegionalPreferencesEntriesFragment.class.getName(),
+            BatteryInfoFragment.class.getName()
     };
 
     public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index f8a5d76..d4acfa1 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -25,11 +25,14 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.View;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceGroup;
@@ -170,6 +173,15 @@
     }
 
     @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        LifecycleOwner viewLifecycleOwner = getViewLifecycleOwner();
+        for (AbstractPreferenceController controller : mControllers) {
+            controller.onViewCreated(viewLifecycleOwner);
+        }
+    }
+
+    @Override
     public void onCategoriesChanged(Set<String> categories) {
         final String categoryKey = getCategoryKey();
         final DashboardCategory dashboardCategory =
diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java
index 3047d73..c3ddb2e 100644
--- a/src/com/android/settings/datausage/BillingCycleSettings.java
+++ b/src/com/android/settings/datausage/BillingCycleSettings.java
@@ -22,8 +22,6 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.Resources;
-import android.icu.text.MeasureFormat;
-import android.icu.util.MeasureUnit;
 import android.net.NetworkPolicy;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
@@ -322,14 +320,10 @@
             final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT);
             final long bytes = isLimit ? editor.getPolicyLimitBytes(template)
                     : editor.getPolicyWarningBytes(template);
-            final long limitDisabled = isLimit ? LIMIT_DISABLED : WARNING_DISABLED;
 
-            final MeasureFormat formatter = MeasureFormat.getInstance(
-                    getContext().getResources().getConfiguration().locale,
-                    MeasureFormat.FormatWidth.SHORT);
             final String[] unitNames = new String[] {
-                formatter.getUnitDisplayName(MeasureUnit.MEGABYTE),
-                formatter.getUnitDisplayName(MeasureUnit.GIGABYTE)
+                    DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), MIB_IN_BYTES),
+                    DataUsageFormatter.INSTANCE.getBytesDisplayUnit(getResources(), GIB_IN_BYTES),
             };
             final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                     getContext(), android.R.layout.simple_spinner_item, unitNames);
diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java
index e47ecbd..6a39234 100644
--- a/src/com/android/settings/datausage/DataSaverBackend.java
+++ b/src/com/android/settings/datausage/DataSaverBackend.java
@@ -196,8 +196,10 @@
     public interface Listener {
         void onDataSaverChanged(boolean isDataSaving);
 
-        void onAllowlistStatusChanged(int uid, boolean isAllowlisted);
+        /** This is called when allow list status is changed. */
+        default void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {}
 
-        void onDenylistStatusChanged(int uid, boolean isDenylisted);
+        /** This is called when deny list status is changed. */
+        default void onDenylistStatusChanged(int uid, boolean isDenylisted) {}
     }
 }
diff --git a/src/com/android/settings/datausage/DataSaverSummary.kt b/src/com/android/settings/datausage/DataSaverSummary.kt
index 1d9cbb7..0828d36 100644
--- a/src/com/android/settings/datausage/DataSaverSummary.kt
+++ b/src/com/android/settings/datausage/DataSaverSummary.kt
@@ -15,33 +15,22 @@
  */
 package com.android.settings.datausage
 
-import android.app.Application
 import android.app.settings.SettingsEnums
 import android.content.Context
 import android.os.Bundle
 import android.telephony.SubscriptionManager
 import android.widget.Switch
-import androidx.lifecycle.lifecycleScope
-import androidx.preference.Preference
 import com.android.settings.R
 import com.android.settings.SettingsActivity
-import com.android.settings.SettingsPreferenceFragment
-import com.android.settings.applications.AppStateBaseBridge
-import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState
+import com.android.settings.dashboard.DashboardFragment
 import com.android.settings.search.BaseSearchIndexProvider
 import com.android.settings.widget.SettingsMainSwitchBar
-import com.android.settingslib.applications.ApplicationsState
 import com.android.settingslib.search.SearchIndexable
-import com.android.settingslib.spa.framework.util.formatString
-import kotlinx.coroutines.launch
 
 @SearchIndexable
-class DataSaverSummary : SettingsPreferenceFragment() {
+class DataSaverSummary : DashboardFragment() {
     private lateinit var switchBar: SettingsMainSwitchBar
     private lateinit var dataSaverBackend: DataSaverBackend
-    private lateinit var unrestrictedAccess: Preference
-    private var dataUsageBridge: AppStateDataUsageBridge? = null
-    private var session: ApplicationsState.Session? = null
 
     // Flag used to avoid infinite loop due if user switch it on/off too quick.
     private var switching = false
@@ -54,8 +43,6 @@
             return
         }
 
-        addPreferencesFromResource(R.xml.data_saver)
-        unrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS)!!
         dataSaverBackend = DataSaverBackend(requireContext())
     }
 
@@ -72,27 +59,12 @@
 
     override fun onResume() {
         super.onResume()
-        dataSaverBackend.refreshAllowlist()
-        dataSaverBackend.refreshDenylist()
         dataSaverBackend.addListener(dataSaverBackendListener)
-        dataUsageBridge?.resume(/* forceLoadAllApps= */ true)
-            ?: viewLifecycleOwner.lifecycleScope.launch {
-                val applicationsState = ApplicationsState.getInstance(
-                    requireContext().applicationContext as Application
-                )
-                dataUsageBridge = AppStateDataUsageBridge(
-                    applicationsState, dataUsageBridgeCallbacks, dataSaverBackend
-                )
-                session =
-                    applicationsState.newSession(applicationsStateCallbacks, settingsLifecycle)
-                dataUsageBridge?.resume(/* forceLoadAllApps= */ true)
-            }
     }
 
     override fun onPause() {
         super.onPause()
         dataSaverBackend.remListener(dataSaverBackendListener)
-        dataUsageBridge?.pause()
     }
 
     private fun onSwitchChanged(isChecked: Boolean) {
@@ -104,9 +76,10 @@
         }
     }
 
+    override fun getPreferenceScreenResId() = R.xml.data_saver
     override fun getMetricsCategory() = SettingsEnums.DATA_SAVER_SUMMARY
-
     override fun getHelpResource() = R.string.help_url_data_saver
+    override fun getLogTag() = TAG
 
     private val dataSaverBackendListener = object : DataSaverBackend.Listener {
         override fun onDataSaverChanged(isDataSaving: Boolean) {
@@ -115,51 +88,10 @@
                 switching = false
             }
         }
-
-        override fun onAllowlistStatusChanged(uid: Int, isAllowlisted: Boolean) {}
-
-        override fun onDenylistStatusChanged(uid: Int, isDenylisted: Boolean) {}
-    }
-
-    private val dataUsageBridgeCallbacks = AppStateBaseBridge.Callback {
-        updateUnrestrictedAccessSummary()
-    }
-
-    private val applicationsStateCallbacks = object : ApplicationsState.Callbacks {
-        override fun onRunningStateChanged(running: Boolean) {}
-
-        override fun onPackageListChanged() {}
-
-        override fun onRebuildComplete(apps: ArrayList<ApplicationsState.AppEntry>?) {}
-
-        override fun onPackageIconChanged() {}
-
-        override fun onPackageSizeChanged(packageName: String?) {}
-
-        override fun onAllSizesComputed() {
-            updateUnrestrictedAccessSummary()
-        }
-
-        override fun onLauncherInfoChanged() {
-            updateUnrestrictedAccessSummary()
-        }
-
-        override fun onLoadEntriesCompleted() {}
-    }
-
-    private fun updateUnrestrictedAccessSummary() {
-        if (!isAdded || isFinishingOrDestroyed) return
-        val allApps = session?.allApps ?: return
-        val count = allApps.count {
-            ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(it) &&
-                (it.extraInfo as? DataUsageState)?.isDataSaverAllowlisted == true
-        }
-        unrestrictedAccess.summary =
-            resources.formatString(R.string.data_saver_unrestricted_summary, "count" to count)
     }
 
     companion object {
-        private const val KEY_UNRESTRICTED_ACCESS = "unrestricted_access"
+        private const val TAG = "DataSaverSummary"
 
         private fun Context.isDataSaverVisible(): Boolean =
             resources.getBoolean(R.bool.config_show_data_saver)
diff --git a/src/com/android/settings/datausage/DataUsageFormatter.kt b/src/com/android/settings/datausage/DataUsageFormatter.kt
new file mode 100644
index 0000000..16a9ae8
--- /dev/null
+++ b/src/com/android/settings/datausage/DataUsageFormatter.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datausage
+
+import android.content.res.Resources
+import android.text.format.Formatter
+
+object DataUsageFormatter {
+
+    /**
+     * Gets the display unit of the given bytes.
+     *
+     * Similar to MeasureFormat.getUnitDisplayName(), but with the expected result for the bytes in
+     * Settings, and align with other places in Settings.
+     */
+    fun Resources.getBytesDisplayUnit(bytes: Long): String =
+        Formatter.formatBytes(this, bytes, Formatter.FLAG_IEC_UNITS).units
+}
\ No newline at end of file
diff --git a/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java
index 9545728..298ced0 100644
--- a/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java
+++ b/src/com/android/settings/development/BluetoothLeAudioDeviceDetailsPreferenceController.java
@@ -40,7 +40,7 @@
 
     private static final String PREFERENCE_KEY = "bluetooth_show_leaudio_device_details";
     private static final String CONFIG_LE_AUDIO_ENABLED_BY_DEFAULT = "le_audio_enabled_by_default";
-    private static final boolean LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE = false;
+    private static final boolean LE_AUDIO_DEVICE_DETAIL_DEFAULT_VALUE = true;
     static int sLeAudioSupportedStateCache = BluetoothStatusCodes.ERROR_UNKNOWN;
 
     @VisibleForTesting
diff --git a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java
index 0d91fdd..b7b2759 100644
--- a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java
+++ b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java
@@ -25,12 +25,4 @@
     int REQUEST_CODE_DEBUG_APP = 1;
 
     int REQUEST_MOCK_LOCATION_APP = 2;
-
-    int REQUEST_CODE_ANGLE_ALL_USE_ANGLE = 3;
-
-    int REQUEST_CODE_ANGLE_DRIVER_PKGS = 4;
-
-    int REQUEST_CODE_ANGLE_DRIVER_VALUES = 5;
-
-    int REQUEST_COMPAT_CHANGE_APP = 6;
 }
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index f7be1aa..047b219 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -675,6 +675,7 @@
         controllers.add(new NfcVerboseVendorLogPreferenceController(context, fragment));
         controllers.add(new ShowTapsPreferenceController(context));
         controllers.add(new PointerLocationPreferenceController(context));
+        controllers.add(new ShowKeyPressesPreferenceController(context));
         controllers.add(new ShowSurfaceUpdatesPreferenceController(context));
         controllers.add(new ShowLayoutBoundsPreferenceController(context));
         controllers.add(new ShowRefreshRatePreferenceController(context));
diff --git a/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java b/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java
index 051cede..f13143d 100644
--- a/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java
+++ b/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java
@@ -29,6 +29,7 @@
 
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.NoSuchElementException;
 
@@ -66,23 +67,34 @@
         return isIDumpstateDeviceAidlServiceAvailable() || isIDumpstateDeviceV1_1ServiceAvailable();
     }
 
+    @SuppressWarnings("FutureReturnValueIgnored")
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         final boolean isEnabled = (Boolean) newValue;
-        setVerboseLoggingEnabled(isEnabled);
+        // IDumpstateDevice IPC may be blocking when system is extremely heavily-loaded.
+        // Post to background thread to avoid ANR. Ignore the returned Future.
+        ThreadUtils.postOnBackgroundThread(() ->
+                setVerboseLoggingEnabled(isEnabled));
         return true;
     }
 
+    @SuppressWarnings("FutureReturnValueIgnored")
     @Override
     public void updateState(Preference preference) {
-        final boolean enabled = getVerboseLoggingEnabled();
-        ((SwitchPreference) mPreference).setChecked(enabled);
+        ThreadUtils.postOnBackgroundThread(() -> {
+                    final boolean enabled = getVerboseLoggingEnabled();
+                    ThreadUtils.getUiThreadHandler().post(() ->
+                            ((SwitchPreference) mPreference).setChecked(enabled));
+                }
+        );
     }
 
+    @SuppressWarnings("FutureReturnValueIgnored")
     @Override
     protected void onDeveloperOptionsSwitchDisabled() {
         super.onDeveloperOptionsSwitchDisabled();
-        setVerboseLoggingEnabled(false);
+        ThreadUtils.postOnBackgroundThread(() ->
+                setVerboseLoggingEnabled(false));
         ((SwitchPreference) mPreference).setChecked(false);
     }
 
diff --git a/src/com/android/settings/development/ShowKeyPressesPreferenceController.java b/src/com/android/settings/development/ShowKeyPressesPreferenceController.java
new file mode 100644
index 0000000..247f59a
--- /dev/null
+++ b/src/com/android/settings/development/ShowKeyPressesPreferenceController.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+/** PreferenceController that controls the "Show key presses" developer option. */
+public class ShowKeyPressesPreferenceController extends
+        DeveloperOptionsPreferenceController implements
+        Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+
+    private static final String SHOW_KEY_PRESSES_KEY = "show_key_presses";
+
+    @VisibleForTesting
+    static final int SETTING_VALUE_ON = 1;
+    @VisibleForTesting
+    static final int SETTING_VALUE_OFF = 0;
+
+    public ShowKeyPressesPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return SHOW_KEY_PRESSES_KEY;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        final boolean isEnabled = (Boolean) newValue;
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        int showKeyPresses = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, SETTING_VALUE_OFF);
+        ((SwitchPreference) mPreference).setChecked(showKeyPresses != SETTING_VALUE_OFF);
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchDisabled() {
+        super.onDeveloperOptionsSwitchDisabled();
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.SHOW_KEY_PRESSES,
+                SETTING_VALUE_OFF);
+        ((SwitchPreference) mPreference).setChecked(false);
+    }
+}
diff --git a/src/com/android/settings/development/compat/PlatformCompatDashboard.java b/src/com/android/settings/development/compat/PlatformCompatDashboard.java
index f8cbf21..3f0ffc7 100644
--- a/src/com/android/settings/development/compat/PlatformCompatDashboard.java
+++ b/src/com/android/settings/development/compat/PlatformCompatDashboard.java
@@ -17,21 +17,16 @@
 package com.android.settings.development.compat;
 
 import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
-import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes.REQUEST_COMPAT_CHANGE_APP;
 
-import android.app.Activity;
-import android.app.AlertDialog;
 import android.app.settings.SettingsEnums;
 import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.text.TextUtils;
 import android.util.ArraySet;
 
 import androidx.annotation.VisibleForTesting;
@@ -40,35 +35,28 @@
 import androidx.preference.PreferenceCategory;
 import androidx.preference.SwitchPreference;
 
-import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.development.AppPicker;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
-
 /**
  * Dashboard for Platform Compat preferences.
  */
 public class PlatformCompatDashboard extends DashboardFragment {
     private static final String TAG = "PlatformCompatDashboard";
-    private static final String COMPAT_APP = "compat_app";
+    public static final String COMPAT_APP = "compat_app";
 
     private IPlatformCompat mPlatformCompat;
 
     private CompatibilityChangeInfo[] mChanges;
 
-    private AndroidBuildClassifier mAndroidBuildClassifier = new AndroidBuildClassifier();
-
-    private boolean mShouldStartAppPickerOnResume = true;
-
     @VisibleForTesting
     String mSelectedApp;
 
@@ -108,32 +96,6 @@
         } catch (RemoteException e) {
             throw new RuntimeException("Could not list changes!", e);
         }
-        if (icicle != null) {
-            mShouldStartAppPickerOnResume = false;
-            mSelectedApp = icicle.getString(COMPAT_APP);
-        }
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == REQUEST_COMPAT_CHANGE_APP) {
-            mShouldStartAppPickerOnResume = false;
-            switch (resultCode) {
-                case Activity.RESULT_OK:
-                    mSelectedApp = data.getAction();
-                    break;
-                case Activity.RESULT_CANCELED:
-                    if (TextUtils.isEmpty(mSelectedApp)) {
-                        finish();
-                    }
-                    break;
-                case AppPicker.RESULT_NO_MATCHING_APPS:
-                    mSelectedApp = null;
-                    break;
-            }
-            return;
-        }
-        super.onActivityResult(requestCode, resultCode, data);
     }
 
     @Override
@@ -142,33 +104,18 @@
         if (isFinishingOrDestroyed()) {
             return;
         }
-        if (!mShouldStartAppPickerOnResume) {
-            if (TextUtils.isEmpty(mSelectedApp)) {
-                new AlertDialog.Builder(getContext())
-                        .setTitle(R.string.platform_compat_dialog_title_no_apps)
-                        .setMessage(R.string.platform_compat_dialog_text_no_apps)
-                        .setPositiveButton(R.string.okay, (dialog, which) -> finish())
-                        .setOnDismissListener(dialog -> finish())
-                        .setCancelable(false)
-                        .show();
-                return;
-            }
-            try {
-                final ApplicationInfo applicationInfo = getApplicationInfo();
-                addPreferences(applicationInfo);
-                return;
-            } catch (PackageManager.NameNotFoundException e) {
-                mShouldStartAppPickerOnResume = true;
-                mSelectedApp = null;
-            }
+        Bundle arguments = getArguments();
+        if (arguments == null) {
+            finish();
+            return;
         }
-        startAppPicker();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putString(COMPAT_APP, mSelectedApp);
+        mSelectedApp = arguments.getString(COMPAT_APP);
+        try {
+            final ApplicationInfo applicationInfo = getApplicationInfo();
+            addPreferences(applicationInfo);
+        } catch (PackageManager.NameNotFoundException ignored) {
+            finish();
+        }
     }
 
     private void addPreferences(ApplicationInfo applicationInfo) {
@@ -266,12 +213,6 @@
         appPreference.setIcon(icon);
         appPreference.setSummary(getString(R.string.platform_compat_selected_app_summary,
                                          mSelectedApp, applicationInfo.targetSdkVersion));
-        appPreference.setKey(mSelectedApp);
-        appPreference.setOnPreferenceClickListener(
-                preference -> {
-                    startAppPicker();
-                    return true;
-                });
         return appPreference;
     }
 
@@ -294,17 +235,6 @@
         }
     }
 
-    private void startAppPicker() {
-        final Intent intent = new Intent(getContext(), AppPicker.class)
-                .putExtra(AppPicker.EXTRA_INCLUDE_NOTHING, false);
-        // If build is neither userdebug nor eng, only include debuggable apps
-        final boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
-        if (!debuggableBuild) {
-            intent.putExtra(AppPicker.EXTRA_DEBUGGABLE, true /* value */);
-        }
-        startActivityForResult(intent, REQUEST_COMPAT_CHANGE_APP);
-    }
-
     private class CompatChangePreferenceChangeListener implements OnPreferenceChangeListener {
         private final long changeId;
 
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java
new file mode 100644
index 0000000..b022fcf
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.fuelgauge.BatteryUtils;
+
+/**
+ * A controller that manages the information about battery cycle count.
+ */
+public class BatteryCycleCountPreferenceController extends BasePreferenceController {
+
+    public BatteryCycleCountPreferenceController(Context context,
+            String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext);
+        final int cycleCount = batteryIntent.getIntExtra(BatteryManager.EXTRA_CYCLE_COUNT, -1);
+
+        return cycleCount == -1
+                ? mContext.getText(R.string.battery_cycle_count_not_available)
+                : Integer.toString(cycleCount);
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryFirstUseDatePreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryFirstUseDatePreferenceController.java
new file mode 100644
index 0000000..6c7a743
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryFirstUseDatePreferenceController.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.os.BatteryManager;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.fuelgauge.BatterySettingsFeatureProvider;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A controller that manages the information about battery first use date.
+ */
+public class BatteryFirstUseDatePreferenceController extends BasePreferenceController {
+
+    private final BatterySettingsFeatureProvider mBatterySettingsFeatureProvider;
+    private final BatteryManager mBatteryManager;
+
+    private long mFirstUseDateInMs;
+
+    public BatteryFirstUseDatePreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+        mBatterySettingsFeatureProvider = FeatureFactory.getFactory(
+                context).getBatterySettingsFeatureProvider();
+        mBatteryManager = mContext.getSystemService(BatteryManager.class);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mBatterySettingsFeatureProvider.isFirstUseDateAvailable(mContext, getFirstUseDate())
+                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return isAvailable()
+                ? BatteryUtils.getBatteryInfoFormattedDate(mFirstUseDateInMs)
+                : null;
+    }
+
+    private long getFirstUseDate() {
+        if (mFirstUseDateInMs == 0L) {
+            final long firstUseDateInSec = mBatteryManager.getLongProperty(
+                    BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE);
+            mFirstUseDateInMs = TimeUnit.MILLISECONDS.convert(firstUseDateInSec, TimeUnit.SECONDS);
+        }
+        return mFirstUseDateInMs;
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java
new file mode 100644
index 0000000..1731212
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/**
+ * A fragment that shows battery hardware information.
+ */
+@SearchIndexable
+public class BatteryInfoFragment extends DashboardFragment {
+
+    public static final String TAG = "BatteryInfo";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.SETTINGS_BATTERY_INFORMATION;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.battery_info;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.battery_info);
+}
diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryManufactureDatePreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryManufactureDatePreferenceController.java
new file mode 100644
index 0000000..ff54c77
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryManufactureDatePreferenceController.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import android.content.Context;
+import android.os.BatteryManager;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.fuelgauge.BatterySettingsFeatureProvider;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A controller that manages the information about battery manufacture date.
+ */
+public class BatteryManufactureDatePreferenceController extends BasePreferenceController {
+
+    private final BatterySettingsFeatureProvider mBatterySettingsFeatureProvider;
+    private final BatteryManager mBatteryManager;
+
+    private long mManufactureDateInMs;
+
+    public BatteryManufactureDatePreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+        mBatterySettingsFeatureProvider = FeatureFactory.getFactory(
+                context).getBatterySettingsFeatureProvider();
+        mBatteryManager = mContext.getSystemService(BatteryManager.class);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mBatterySettingsFeatureProvider.isManufactureDateAvailable(mContext,
+                getManufactureDate())
+                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return isAvailable()
+                ? BatteryUtils.getBatteryInfoFormattedDate(mManufactureDateInMs)
+                : null;
+    }
+
+    private long getManufactureDate() {
+        if (mManufactureDateInMs == 0L) {
+            final long manufactureDateInSec = mBatteryManager.getLongProperty(
+                    BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE);
+            mManufactureDateInMs = TimeUnit.MILLISECONDS.convert(manufactureDateInSec,
+                    TimeUnit.SECONDS);
+        }
+        return mManufactureDateInMs;
+    }
+}
diff --git a/src/com/android/settings/display/StayAwakeOnFoldPreferenceController.java b/src/com/android/settings/display/StayAwakeOnFoldPreferenceController.java
new file mode 100644
index 0000000..9df48f3
--- /dev/null
+++ b/src/com/android/settings/display/StayAwakeOnFoldPreferenceController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+/**
+ * A preference controller for the "Stay unlocked on fold" setting.
+ *
+ * This preference controller allows users to control whether or not the device
+ * stays awake when it is folded. When this setting is enabled, the device will
+ * stay awake even if the device is folded.
+ *
+ * @link android.provider.Settings.System#STAY_AWAKE_ON_FOLD
+ */
+public class StayAwakeOnFoldPreferenceController extends TogglePreferenceController {
+
+    private final Resources mResources;
+
+    public StayAwakeOnFoldPreferenceController(Context context, String key) {
+        this(context, key, context.getResources());
+    }
+
+    public StayAwakeOnFoldPreferenceController(Context context, String key, Resources resources) {
+        super(context, key);
+        mResources = resources;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mResources.getBoolean(R.bool.config_stay_awake_on_fold) ? AVAILABLE
+                : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public boolean isChecked() {
+        return Settings.System.getInt(
+                mContext.getContentResolver(),
+                Settings.System.STAY_AWAKE_ON_FOLD,
+                0) == 1;
+    }
+
+    @Override
+    public boolean setChecked(boolean isChecked) {
+        final int stayUnlockedOnFold = isChecked ? 1 : 0;
+
+        return Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.STAY_AWAKE_ON_FOLD, stayUnlockedOnFold);
+    }
+
+    @Override
+    public int getSliceHighlightMenuRes() {
+        return R.string.menu_key_display;
+    }
+
+}
diff --git a/src/com/android/settings/dream/WhenToDreamPicker.java b/src/com/android/settings/dream/WhenToDreamPicker.java
index 13cdadf..3052d20 100644
--- a/src/com/android/settings/dream/WhenToDreamPicker.java
+++ b/src/com/android/settings/dream/WhenToDreamPicker.java
@@ -50,7 +50,7 @@
 
     @Override
     public int getMetricsCategory() {
-        return SettingsEnums.DREAM;
+        return SettingsEnums.SETTINGS_WHEN_TO_DREAM;
     }
 
     @Override
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index 79e0194..d38dede 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.fuelgauge;
 
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserConsumer;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.backup.BackupManager;
@@ -41,7 +43,6 @@
 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
 import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
 import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
-import com.android.settings.fuelgauge.batteryusage.BatteryHistEntry;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.EntityHeaderController;
 import com.android.settingslib.HelpUtils;
@@ -149,14 +150,13 @@
             Context context, int sourceMetricsCategory,
             BatteryDiffEntry diffEntry, String usagePercent, String slotInformation,
             boolean showTimeInformation) {
-        final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
         final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
         // configure the launch argument.
         launchArgs.mUsagePercent = usagePercent;
         launchArgs.mPackageName = diffEntry.getPackageName();
         launchArgs.mAppLabel = diffEntry.getAppLabel();
         launchArgs.mSlotInformation = slotInformation;
-        launchArgs.mUid = (int) histEntry.mUid;
+        launchArgs.mUid = (int) diffEntry.mUid;
         launchArgs.mIconId = diffEntry.getAppIconId();
         launchArgs.mConsumedPower = (int) diffEntry.mConsumePower;
         if (showTimeInformation) {
@@ -164,7 +164,7 @@
             launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs;
             launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs;
         }
-        launchArgs.mIsUserEntry = histEntry.isUserEntry();
+        launchArgs.mIsUserEntry = isUserConsumer(diffEntry.mConsumerType);
         startBatteryDetailPage(context, sourceMetricsCategory, launchArgs);
     }
 
@@ -289,12 +289,14 @@
         mLogStringBuilder.append(", onPause mode = ").append(selectedPreference);
         logMetricCategory(selectedPreference);
 
-        BatteryHistoricalLogUtil.writeLog(
-                getContext().getApplicationContext(),
-                Action.LEAVE,
-                BatteryHistoricalLogUtil.getPackageNameWithUserId(
-                        mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()),
-                mLogStringBuilder.toString());
+        mExecutor.execute(() -> {
+            BatteryOptimizeLogUtils.writeLog(
+                    getContext().getApplicationContext(),
+                    Action.LEAVE,
+                    BatteryOptimizeLogUtils.getPackageNameWithUserId(
+                            mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()),
+                    mLogStringBuilder.toString());
+        });
         Log.d(TAG, "Leave with mode: " + selectedPreference);
     }
 
diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
index 66ffc90..50f1b90 100644
--- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
+++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
@@ -199,7 +199,7 @@
                     info.packageName + DELIMITER_MODE + optimizationMode;
             builder.append(packageOptimizeMode + DELIMITER);
             Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode);
-            BatteryHistoricalLogUtil.writeLog(
+            BatteryOptimizeLogUtils.writeLog(
                     sharedPreferences, Action.BACKUP, info.packageName,
                     /* actionDescription */ "mode: " + optimizationMode);
             backupCount++;
@@ -275,7 +275,7 @@
 
     /** Dump the app optimization mode backup history data. */
     public static void dumpHistoricalData(Context context, PrintWriter writer) {
-        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(
+        BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(
                 getSharedPreferences(context), writer);
     }
 
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java b/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtils.java
similarity index 89%
rename from src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java
rename to src/com/android/settings/fuelgauge/BatteryOptimizeLogUtils.java
index f82b703..d093d35 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtils.java
@@ -20,23 +20,25 @@
 import android.content.SharedPreferences;
 import android.util.Base64;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
 import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
 
-import com.google.common.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
 import java.util.List;
 
 /** Writes and reads a historical log of battery related state change events. */
-public final class BatteryHistoricalLogUtil {
+public final class BatteryOptimizeLogUtils {
+    private static final String TAG = "BatteryOptimizeLogUtils";
     private static final String BATTERY_OPTIMIZE_FILE_NAME = "battery_optimize_historical_logs";
     private static final String LOGS_KEY = "battery_optimize_logs_key";
-    private static final String TAG = "BatteryHistoricalLogUtil";
 
     @VisibleForTesting
     static final int MAX_ENTRIES = 40;
 
+    private BatteryOptimizeLogUtils() {}
+
     /** Writes a log entry for battery optimization mode. */
     static void writeLog(
             Context context, Action action, String packageName, String actionDescription) {
@@ -67,7 +69,7 @@
         newLogBuilder.addLogEntry(logEntry);
 
         String loggingContent =
-            Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT);
+                Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT);
         sharedPreferences
                 .edit()
                 .putString(LOGS_KEY, loggingContent)
@@ -94,7 +96,7 @@
         if (logEntryList.isEmpty()) {
             writer.println("\tnothing to dump");
         } else {
-            writer.println("0:UNKNOWN 1:RESTRICTED  2:UNRESTRICTED 3:OPTIMIZED");
+            writer.println("0:UNKNOWN 1:RESTRICTED 2:UNRESTRICTED 3:OPTIMIZED");
             logEntryList.forEach(entry -> writer.println(toString(entry)));
         }
     }
@@ -113,6 +115,7 @@
 
     @VisibleForTesting
     static SharedPreferences getSharedPreferences(Context context) {
-        return context.getSharedPreferences(BATTERY_OPTIMIZE_FILE_NAME, Context.MODE_PRIVATE);
+        return context.getApplicationContext()
+                .getSharedPreferences(BATTERY_OPTIMIZE_FILE_NAME, Context.MODE_PRIVATE);
     }
 }
diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
index 589e1fd..124840e 100644
--- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
@@ -245,7 +245,7 @@
             Context context, int appStandbyMode, boolean allowListed, int uid, String packageName,
             BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend,
             Action action) {
-        final String packageNameKey = BatteryHistoricalLogUtil
+        final String packageNameKey = BatteryOptimizeLogUtils
                 .getPackageNameWithUserId(packageName, UserHandle.myUserId());
         try {
             batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode);
@@ -259,7 +259,7 @@
             appStandbyMode = -1;
             Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e);
         }
-        BatteryHistoricalLogUtil.writeLog(
+        BatteryOptimizeLogUtils.writeLog(
                 context,
                 action,
                 packageNameKey,
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java
index f6efb24..260fde0 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java
@@ -16,9 +16,14 @@
 
 package com.android.settings.fuelgauge;
 
-import android.content.ComponentName;
+import android.content.Context;
 
 /** Feature provider for battery settings usage. */
 public interface BatterySettingsFeatureProvider {
 
+    /** Returns true if manufacture date should be shown */
+    boolean isManufactureDateAvailable(Context context, long manufactureDateMs);
+
+    /** Returns true if first use date should be shown */
+    boolean isFirstUseDateAvailable(Context context, long firstUseDateMs);
 }
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
index 39fe118..6b456b7 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
@@ -21,9 +21,13 @@
 /** Feature provider implementation for battery settings usage. */
 public class BatterySettingsFeatureProviderImpl implements BatterySettingsFeatureProvider {
 
-    protected Context mContext;
+    @Override
+    public boolean isManufactureDateAvailable(Context context, long manufactureDateMs) {
+        return false;
+    }
 
-    public BatterySettingsFeatureProviderImpl(Context context) {
-        mContext = context.getApplicationContext();
+    @Override
+    public boolean isFirstUseDateAvailable(Context context, long firstUseDateMs) {
+        return false;
     }
 }
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
index 4b9e6ef..8697e43 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
@@ -16,8 +16,8 @@
 
 package com.android.settings.fuelgauge;
 
-import android.content.ContentResolver;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.provider.Settings;
@@ -25,8 +25,6 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry;
 import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 
@@ -41,6 +39,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "onReceive: " + intent + " owner: " + BatteryBackupHelper.isOwner());
         if (intent != null
                 && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
                 && BatteryBackupHelper.isOwner()) {
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index 12760b1..1f7e3ec 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -64,8 +64,10 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.time.Duration;
 import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.List;
 
 /**
@@ -353,7 +355,7 @@
     @SuppressWarnings("unchecked")
     public static <T extends MessageLite> T parseProtoFromString(
             String serializedProto, T protoClass) {
-        if (serializedProto.isEmpty()) {
+        if (serializedProto == null || serializedProto.isEmpty()) {
             return (T) protoClass.getDefaultInstanceForType();
         }
         try {
@@ -451,12 +453,10 @@
 
     @VisibleForTesting
     Estimate getEnhancedEstimate() {
-        Estimate estimate = null;
-        // Get enhanced prediction if available
-        if (Duration.between(Estimate.getLastCacheUpdateTime(mContext), Instant.now())
-                .compareTo(Duration.ofSeconds(10)) < 0) {
-            estimate = Estimate.getCachedEstimateIfAvailable(mContext);
-        } else if (mPowerUsageFeatureProvider != null &&
+        // Align the same logic in the BatteryControllerImpl.updateEstimate()
+        Estimate estimate = Estimate.getCachedEstimateIfAvailable(mContext);
+        if (estimate == null &&
+                mPowerUsageFeatureProvider != null &&
                 mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(mContext)) {
             estimate = mPowerUsageFeatureProvider.getEnhancedBatteryPrediction(mContext);
             if (estimate != null) {
@@ -673,6 +673,14 @@
         }
         return summary.toString();
     }
+    /** Format the date of battery related info */
+    public static CharSequence getBatteryInfoFormattedDate(long dateInMs) {
+        final Instant instant = Instant.ofEpochMilli(dateInMs);
+        final String localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate().format(
+                DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));
+
+        return localDate;
+    }
 
     /** Builds the battery usage time information for one timestamp. */
     private static String buildBatteryUsageTimeInfo(final Context context, long timeInMs,
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index 0b0e243..30eabfa 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -18,9 +18,11 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.util.ArrayMap;
 import android.util.SparseIntArray;
 
+import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
 import com.android.settingslib.fuelgauge.Estimate;
 
 import java.util.List;
@@ -37,6 +39,16 @@
     boolean isBatteryUsageEnabled();
 
     /**
+     * Check whether the battery tips card is enabled in the battery usage page
+     */
+    boolean isBatteryTipsEnabled();
+
+    /**
+     * Check whether the feedback card is enabled in the battery tips card
+     */
+    boolean isBatteryTipsFeedbackEnabled();
+
+    /**
      * Returns a threshold (in milliseconds) for the minimal screen on time in battery usage list
      */
     double getBatteryUsageListScreenOnTimeThresholdInMs();
@@ -129,6 +141,16 @@
     boolean delayHourlyJobWhenBooting();
 
     /**
+     * Insert settings configuration data for anomaly detection
+     */
+    void insertSettingsData(Context context, double displayDrain);
+
+    /**
+     * Returns {@link Bundle} for settings anomaly detection result
+     */
+    PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain);
+
+    /**
      * Gets an intent for one time bypass charge limited to resume charging.
      */
     Intent getResumeChargeIntent(boolean isDockDefender);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 1d0ba18..127178a 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -27,6 +27,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
 import com.android.settingslib.fuelgauge.Estimate;
 
 import java.util.ArrayList;
@@ -75,6 +76,16 @@
     }
 
     @Override
+    public boolean isBatteryTipsEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isBatteryTipsFeedbackEnabled() {
+        return false;
+    }
+
+    @Override
     public double getBatteryUsageListScreenOnTimeThresholdInMs() {
         return 0;
     }
@@ -161,6 +172,14 @@
     }
 
     @Override
+    public void insertSettingsData(Context context, double displayDrain) {}
+
+    @Override
+    public PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain) {
+        return null;
+    }
+
+    @Override
     public Set<Integer> getOthersSystemComponentSet() {
         return new ArraySet<>();
     }
diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
index 254cf04..e08f4ba 100644
--- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.BatteryManager;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -139,7 +140,10 @@
         if (Utils.containsIncompatibleChargers(mContext, TAG)) {
             return mContext.getString(R.string.battery_info_status_not_charging);
         }
-        if (!info.discharging && info.chargeLabel != null) {
+        if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+            // Present status only if no remaining time or status anomalous
+            return info.statusLabel;
+        } else if (!info.discharging && info.chargeLabel != null) {
             return info.chargeLabel;
         } else if (info.remainingLabel == null) {
             return info.batteryPercentString;
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
index 8aabc37..fdafca6 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
@@ -20,9 +20,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.SparseIntArray;
-import android.view.View;
 
-import androidx.annotation.IdRes;
+import androidx.annotation.DrawableRes;
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -134,7 +133,8 @@
 
     public abstract CharSequence getSummary(Context context);
 
-    @IdRes
+    /** Gets the drawable resource id for the icon. */
+    @DrawableRes
     public abstract int getIconId();
 
     /**
@@ -162,21 +162,12 @@
         preference.setTitle(getTitle(context));
         preference.setSummary(getSummary(context));
         preference.setIcon(getIconId());
-        @IdRes int iconTintColorId = getIconTintColorId();
-        if (iconTintColorId != View.NO_ID) {
-            preference.getIcon().setTint(context.getColor(iconTintColorId));
-        }
         final CardPreference cardPreference = castToCardPreferenceSafely(preference);
         if (cardPreference != null) {
             cardPreference.resetLayoutState();
         }
     }
 
-    /** Returns the color resid for tinting {@link #getIconId()} or {@link View#NO_ID} if none. */
-    public @IdRes int getIconTintColorId() {
-        return View.NO_ID;
-    }
-
     public boolean shouldShowDialog() {
         return mShowDialog;
     }
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
index 1c5616f..48cfb7a 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
@@ -52,7 +52,7 @@
 
     @Override
     public int getIconId() {
-        return R.drawable.ic_battery_alert_theme;
+        return R.drawable.ic_battery_charger;
     }
 
     @Override
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java
deleted file mode 100644
index c336fcd..0000000
--- a/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java
+++ /dev/null
@@ -1,83 +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.fuelgauge.batteryusage;
-
-import android.app.usage.UsageEvents;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/** Load app usage events data in the background. */
-public final class AppUsageDataLoader {
-    private static final String TAG = "AppUsageDataLoader";
-
-    // For testing only.
-    @VisibleForTesting
-    static Supplier<Map<Long, UsageEvents>> sFakeAppUsageEventsSupplier;
-    @VisibleForTesting
-    static Supplier<List<AppUsageEvent>> sFakeUsageEventsListSupplier;
-
-    private AppUsageDataLoader() {}
-
-    static void enqueueWork(final Context context) {
-        AsyncTask.execute(() -> {
-            Log.d(TAG, "loadAppUsageDataSafely() in the AsyncTask");
-            loadAppUsageDataSafely(context.getApplicationContext());
-        });
-    }
-
-    @VisibleForTesting
-    static void loadAppUsageData(final Context context) {
-        final long start = System.currentTimeMillis();
-        final Map<Long, UsageEvents> appUsageEvents =
-                sFakeAppUsageEventsSupplier != null
-                        ? sFakeAppUsageEventsSupplier.get()
-                        : DataProcessor.getAppUsageEvents(context);
-        if (appUsageEvents == null) {
-            Log.w(TAG, "loadAppUsageData() returns null");
-            return;
-        }
-        final List<AppUsageEvent> appUsageEventList =
-                sFakeUsageEventsListSupplier != null
-                        ? sFakeUsageEventsListSupplier.get()
-                        : DataProcessor.generateAppUsageEventListFromUsageEvents(
-                                context, appUsageEvents);
-        if (appUsageEventList == null || appUsageEventList.isEmpty()) {
-            Log.w(TAG, "loadAppUsageData() returns null or empty content");
-            return;
-        }
-        final long elapsedTime = System.currentTimeMillis() - start;
-        Log.d(TAG, String.format("loadAppUsageData() size=%d in %d/ms", appUsageEventList.size(),
-                elapsedTime));
-        // Uploads the AppUsageEvent data into database.
-        DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
-    }
-
-    private static void loadAppUsageDataSafely(final Context context) {
-        try {
-            loadAppUsageData(context);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "loadAppUsageData:" + e);
-        }
-    }
-}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 17d9c8a..e926f79 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -36,6 +36,7 @@
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -43,6 +44,7 @@
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnCreate;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnPause;
 import com.android.settingslib.core.lifecycle.events.OnResume;
 import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
 
@@ -50,12 +52,17 @@
 
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /** Controls the update for chart graph and the list items. */
 public class BatteryChartPreferenceController extends AbstractPreferenceController
-        implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy,
+        implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy, OnPause,
         OnSaveInstanceState, OnResume {
     private static final String TAG = "BatteryChartPreferenceController";
     private static final String PREFERENCE_KEY = "battery_chart";
@@ -98,6 +105,19 @@
         void onScreenOnTimeUpdated(Long screenOnTime, String slotTimestamp);
     }
 
+    /**
+     * A callback listener for the battery tips card is updated.
+     * This happens when battery tips card is ready.
+     */
+    public interface OnBatteryTipsUpdatedListener {
+        /**
+         * The callback function for the battery tips card is updated.
+         * @param powerAnomalyEvent the power anomaly event with highest score
+         */
+        void onBatteryTipsUpdated(PowerAnomalyEvent powerAnomalyEvent);
+    }
+
+
     @VisibleForTesting
     Context mPrefContext;
     @VisibleForTesting
@@ -112,21 +132,24 @@
     Map<Integer, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
 
     private boolean mIs24HourFormat;
-    private boolean mHourlyChartVisible = true;
     private View mBatteryChartViewGroup;
     private TextView mChartSummaryTextView;
     private BatteryChartViewModel mDailyViewModel;
     private List<BatteryChartViewModel> mHourlyViewModels;
     private OnBatteryUsageUpdatedListener mOnBatteryUsageUpdatedListener;
     private OnScreenOnTimeUpdatedListener mOnScreenOnTimeUpdatedListener;
+    private OnBatteryTipsUpdatedListener mOnBatteryTipsUpdatedListener;
+    private AtomicBoolean mIsAppResume = new AtomicBoolean(false);
 
     private final SettingsActivity mActivity;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private final PowerUsageFeatureProvider mPowerUsageFeatureProvider;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final AnimatorListenerAdapter mHourlyChartFadeInAdapter =
             createHourlyChartAnimatorListenerAdapter(/*visible=*/ true);
     private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter =
             createHourlyChartAnimatorListenerAdapter(/*visible=*/ false);
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
 
     @VisibleForTesting
     final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator =
@@ -142,6 +165,8 @@
         mIs24HourFormat = DateFormat.is24HourFormat(context);
         mMetricsFeatureProvider =
                 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
+        mPowerUsageFeatureProvider =
+                FeatureFactory.getFactory(mContext).getPowerUsageFeatureProvider(context);
         if (lifecycle != null) {
             lifecycle.addObserver(this);
         }
@@ -159,9 +184,15 @@
         Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d",
                 mDailyChartIndex, mHourlyChartIndex));
     }
+    @Override
+    public void onPause() {
+        mIsAppResume.compareAndSet(/* expect= */ true, /* update= */ false);
+    }
+
 
     @Override
     public void onResume() {
+        mIsAppResume.compareAndSet(/* expect= */ false, /* update= */ true);
         mIs24HourFormat = DateFormat.is24HourFormat(mContext);
         mMetricsFeatureProvider.action(mPrefContext, SettingsEnums.OPEN_BATTERY_USAGE);
     }
@@ -209,20 +240,12 @@
         mOnScreenOnTimeUpdatedListener = listener;
     }
 
-    void setBatteryHistoryMap(
-            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
-        Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
-                : ("size=" + batteryHistoryMap.size())));
-        // Ensure the battery chart group is visible for users.
-        animateBatteryChartViewGroup();
-        final BatteryLevelData batteryLevelData =
-                DataProcessManager.getBatteryLevelData(mContext, mHandler, batteryHistoryMap,
-                        batteryUsageMap -> {
-                            mBatteryUsageMap = batteryUsageMap;
-                            logScreenUsageTime();
-                            refreshUi();
-                        });
-        Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
+    void setOnBatteryTipsUpdatedListener(OnBatteryTipsUpdatedListener listener) {
+        mOnBatteryTipsUpdatedListener = listener;
+    }
+
+    void onBatteryLevelDataUpdate(final BatteryLevelData batteryLevelData) {
+        Log.d(TAG, "onBatteryLevelDataUpdate: " + batteryLevelData);
         mMetricsFeatureProvider.action(
                 mPrefContext,
                 SettingsEnums.ACTION_BATTERY_HISTORY_LOADED,
@@ -253,6 +276,13 @@
         refreshUi();
     }
 
+    void onBatteryUsageMapUpdate(Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
+        Log.d(TAG, "onBatteryUsageMapUpdate: " + batteryUsageMap);
+        mBatteryUsageMap = batteryUsageMap;
+        logScreenUsageTime();
+        refreshUi();
+    }
+
     void setBatteryChartView(@NonNull final BatteryChartView dailyChartView,
             @NonNull final BatteryChartView hourlyChartView) {
         final View parentView = (View) dailyChartView.getParent();
@@ -344,10 +374,37 @@
             }
             mOnBatteryUsageUpdatedListener.onBatteryUsageUpdated(
                     slotUsageData, getSlotInformation(), isBatteryUsageMapNullOrEmpty());
+
+            Log.d(TAG, "isBatteryTipsEnabled = "
+                    + mPowerUsageFeatureProvider.isBatteryTipsEnabled());
+            if (mOnBatteryTipsUpdatedListener != null) {
+                mExecutor.execute(() -> {
+                    final PowerAnomalyEventList anomalyEventList = mPowerUsageFeatureProvider
+                            .detectSettingsAnomaly(mContext, /* displayDrain= */ 0);
+                    Log.d(TAG, "anomalyEventList = " + anomalyEventList);
+                    final PowerAnomalyEvent displayEvent =
+                            getHighestScoreAnomalyEvent(anomalyEventList);
+                    mHandler.post(() -> {
+                                if (mIsAppResume.get()) {
+                                    mOnBatteryTipsUpdatedListener
+                                            .onBatteryTipsUpdated(displayEvent);
+                                }
+                            }
+                    );
+                });
+            }
         }
         return true;
     }
 
+    private PowerAnomalyEvent getHighestScoreAnomalyEvent(PowerAnomalyEventList anomalyEventList) {
+        if (anomalyEventList == null || anomalyEventList.getPowerAnomalyEventsCount() == 0) {
+            return null;
+        }
+        return Collections.max(anomalyEventList.getPowerAnomalyEventsList(),
+                Comparator.comparing(PowerAnomalyEvent::getScore));
+    }
+
     private boolean refreshUiWithNoLevelDataCase() {
         setChartSummaryVisible(false);
         if (mBatteryUsageMap == null) {
@@ -449,10 +506,10 @@
     }
 
     private void animateBatteryHourlyChartView(final boolean visible) {
-        if (mHourlyChartView == null || mHourlyChartVisible == visible) {
+        if (mHourlyChartView == null
+                || (mHourlyChartView.getVisibility() == View.VISIBLE) == visible) {
             return;
         }
-        mHourlyChartVisible = visible;
 
         if (visible) {
             mHourlyChartView.setVisibility(View.VISIBLE);
@@ -609,10 +666,8 @@
             return null;
         }
         for (BatteryDiffEntry entry : entries) {
-            final BatteryHistEntry batteryHistEntry = entry.mBatteryHistEntry;
-            if (batteryHistEntry != null
-                    && batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
-                    && batteryHistEntry.mUserId == userId
+            if (!entry.isSystemEntry()
+                    && entry.mUserId == userId
                     && packageName.equals(entry.getPackageName())) {
                 return entry;
             }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
index 445a5d1..086f56c 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
@@ -17,7 +17,9 @@
 
 import static com.android.settings.Utils.formatPercentage;
 import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS;
+import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
 
+import static java.lang.Math.abs;
 import static java.lang.Math.round;
 import static java.util.Objects.requireNonNull;
 
@@ -61,6 +63,7 @@
     private static final String TAG = "BatteryChartView";
 
     private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
+    private static final int HORIZONTAL_DIVIDER_COUNT = 5;
 
     /** A callback listener for selected group index is updated. */
     public interface OnSelectListener {
@@ -73,6 +76,8 @@
     private final Rect[] mPercentageBounds = new Rect[]{new Rect(), new Rect(), new Rect()};
     private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
     private final Set<Integer> mLabelDrawnIndexes = new ArraySet<>();
+    private final int mLayoutDirection =
+            getContext().getResources().getConfiguration().getLayoutDirection();
 
     private BatteryChartViewModel mViewModel;
     private int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
@@ -158,7 +163,12 @@
             }
             // Updates the indent configurations.
             mIndent.top = mPercentageBounds[0].height();
-            mIndent.right = mPercentageBounds[0].width() + mTextPadding;
+            final int textWidth = mPercentageBounds[0].width() + mTextPadding;
+            if (isRTL()) {
+                mIndent.left = textWidth;
+            } else {
+                mIndent.right = textWidth;
+            }
 
             if (mViewModel != null) {
                 int maxTop = 0;
@@ -333,25 +343,27 @@
     }
 
     private void drawHorizontalDividers(Canvas canvas) {
-        final int width = getWidth() - mIndent.right;
+        final int width = getWidth() - abs(mIndent.width());
         final int height = getHeight() - mIndent.top - mIndent.bottom;
-        // Draws the top divider line for 100% curve.
-        float offsetY = mIndent.top + mDividerWidth * .5f;
+        final float topOffsetY = mIndent.top + mDividerWidth * .5f;
+        final float bottomOffsetY = mIndent.top + (height - mDividerHeight - mDividerWidth * .5f);
+        final float availableSpace = bottomOffsetY - topOffsetY;
+
         mDividerPaint.setColor(DIVIDER_COLOR);
-        canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
-        drawPercentage(canvas, /*index=*/ 0, offsetY);
+        final float dividerOffsetUnit =
+                availableSpace / (float) (HORIZONTAL_DIVIDER_COUNT - 1);
 
-        // Draws the center divider line for 50% curve.
-        final float availableSpace =
-                height - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight;
-        offsetY = mIndent.top + mDividerWidth + availableSpace * .5f;
-        canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
-        drawPercentage(canvas, /*index=*/ 1, offsetY);
+        // Draws 5 divider lines.
+        for (int index = 0; index < HORIZONTAL_DIVIDER_COUNT; index++) {
+            float offsetY = topOffsetY + dividerOffsetUnit * index;
+            canvas.drawLine(mIndent.left, offsetY,
+                    mIndent.left + width, offsetY, mDividerPaint);
 
-        // Draws the bottom divider line for 0% curve.
-        offsetY = mIndent.top + (height - mDividerHeight - mDividerWidth * .5f);
-        canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
-        drawPercentage(canvas, /*index=*/ 2, offsetY);
+            //  Draws percentage text only for 100% / 50% / 0%
+            if (index % 2 == 0) {
+                drawPercentage(canvas, /*index=*/ (index + 1) / 2, offsetY);
+            }
+        }
     }
 
     private void drawPercentage(Canvas canvas, int index, float offsetY) {
@@ -360,14 +372,14 @@
             mTextPaint.setColor(mDefaultTextColor);
             canvas.drawText(
                     mPercentages[index],
-                    getWidth(),
+                    isRTL() ? mIndent.left - mTextPadding : getWidth(),
                     offsetY + mPercentageBounds[index].height() * .5f,
                     mTextPaint);
         }
     }
 
     private void drawVerticalDividers(Canvas canvas) {
-        final int width = getWidth() - mIndent.right;
+        final int width = getWidth() - abs(mIndent.width());
         final int dividerCount = mTrapezoidSlots.length + 1;
         final float dividerSpace = dividerCount * mDividerWidth;
         final float unitWidth = (width - dividerSpace) / (float) mTrapezoidSlots.length;
@@ -382,7 +394,7 @@
                 case CENTER_OF_TRAPEZOIDS:
                     axisLabelDisplayAreas = getAxisLabelDisplayAreas(
                             /* size= */ mViewModel.size() - 1,
-                            /* baselineX= */ mDividerWidth + unitWidth * .5f,
+                            /* baselineX= */ mIndent.left + mDividerWidth + unitWidth * .5f,
                             /* offsetX= */ mDividerWidth + unitWidth,
                             baselineY,
                             /* shiftFirstAndLast= */ false);
@@ -391,7 +403,7 @@
                 default:
                     axisLabelDisplayAreas = getAxisLabelDisplayAreas(
                             /* size= */ mViewModel.size(),
-                            /* baselineX= */ mDividerWidth * .5f,
+                            /* baselineX= */ mIndent.left + mDividerWidth * .5f,
                             /* offsetX= */ mDividerWidth + unitWidth,
                             baselineY,
                             /* shiftFirstAndLast= */ true);
@@ -400,7 +412,7 @@
             drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
         }
         // Draws each vertical dividers.
-        float startX = mDividerWidth * .5f;
+        float startX = mDividerWidth * .5f + mIndent.left;
         for (int index = 0; index < dividerCount; index++) {
             float dividerY = bottomY;
             if (mViewModel.axisLabelPosition() == BETWEEN_TRAPEZOIDS
@@ -414,8 +426,9 @@
             final float nextX = startX + mDividerWidth + unitWidth;
             // Updates the trapezoid slots for drawing.
             if (index < mTrapezoidSlots.length) {
-                mTrapezoidSlots[index].mLeft = round(startX + trapezoidSlotOffset);
-                mTrapezoidSlots[index].mRight = round(nextX - trapezoidSlotOffset);
+                final int trapezoidIndex = isRTL() ? mTrapezoidSlots.length - index - 1 : index;
+                mTrapezoidSlots[trapezoidIndex].mLeft = round(startX + trapezoidSlotOffset);
+                mTrapezoidSlots[trapezoidIndex].mRight = round(nextX - trapezoidSlotOffset);
             }
             startX = nextX;
         }
@@ -507,10 +520,20 @@
         return displayAreas[leftIndex].right + mTextPadding * 2.3f > displayAreas[rightIndex].left;
     }
 
+    private boolean isRTL() {
+        return mLayoutDirection == View.LAYOUT_DIRECTION_RTL;
+    }
+
     private void drawAxisLabelText(
-            Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
+            Canvas canvas, int index, final Rect displayArea, final float baselineY) {
         mTextPaint.setColor(mTrapezoidSolidColor);
         mTextPaint.setTextAlign(Paint.Align.CENTER);
+        // Reverse the sort of axis labels for RTL
+        if (isRTL()) {
+            index = mViewModel.axisLabelPosition() == BETWEEN_TRAPEZOIDS
+                            ? mViewModel.size() - index - 1     // for hourly
+                            : mViewModel.size() - index - 2;    // for daily
+        }
         canvas.drawText(
                 mViewModel.getText(index),
                 displayArea.centerX(),
@@ -546,10 +569,16 @@
                     mHoveredIndex);
             mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
 
-            final float leftTop = round(
+            float leftTop = round(
                     trapezoidBottom - requireNonNull(mViewModel.getLevel(index)) * unitHeight);
-            final float rightTop = round(trapezoidBottom
+            float rightTop = round(trapezoidBottom
                     - requireNonNull(mViewModel.getLevel(index + 1)) * unitHeight);
+            // Mirror the shape of the trapezoid for RTL
+            if (isRTL()) {
+                float temp = leftTop;
+                leftTop = rightTop;
+                rightTop = temp;
+            }
             trapezoidPath.reset();
             trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
             trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -587,8 +616,8 @@
 
     private static boolean isTrapezoidValid(
             @NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
-        return viewModel.getLevel(trapezoidIndex) != null
-                && viewModel.getLevel(trapezoidIndex + 1) != null;
+        return viewModel.getLevel(trapezoidIndex) != BATTERY_LEVEL_UNKNOWN
+                && viewModel.getLevel(trapezoidIndex + 1) != BATTERY_LEVEL_UNKNOWN;
     }
 
     private static boolean isTrapezoidIndexValid(
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
index 47ae568..53861e3 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
+
 import android.content.Context;
 import android.os.BatteryConsumer;
 
@@ -34,6 +36,10 @@
 public class BatteryDiffData {
     static final double SMALL_PERCENTAGE_THRESHOLD = 1f;
 
+    private final long mStartTimestamp;
+    private final long mEndTimestamp;
+    private final int mStartBatteryLevel;
+    private final int mEndBatteryLevel;
     private final long mScreenOnTime;
     private final List<BatteryDiffEntry> mAppEntries;
     private final List<BatteryDiffEntry> mSystemEntries;
@@ -41,12 +47,20 @@
     /** Constructor for the diff entries. */
     public BatteryDiffData(
             final Context context,
+            final long startTimestamp,
+            final long endTimestamp,
+            final int startBatteryLevel,
+            final int endBatteryLevel,
             final long screenOnTime,
             final @NonNull List<BatteryDiffEntry> appDiffEntries,
             final @NonNull List<BatteryDiffEntry> systemDiffEntries,
             final @NonNull Set<String> systemAppsPackageNames,
             final @NonNull Set<Integer> systemAppsUids,
             final boolean isAccumulated) {
+        mStartTimestamp = startTimestamp;
+        mEndTimestamp = endTimestamp;
+        mStartBatteryLevel = startBatteryLevel;
+        mEndBatteryLevel = endBatteryLevel;
         mScreenOnTime = screenOnTime;
         mAppEntries = appDiffEntries;
         mSystemEntries = systemDiffEntries;
@@ -63,18 +77,48 @@
         processAndSortEntries(mSystemEntries);
     }
 
-    public long getScreenOnTime() {
+    long getStartTimestamp() {
+        return mStartTimestamp;
+    }
+
+    long getEndTimestamp() {
+        return mEndTimestamp;
+    }
+
+    int getStartBatteryLevel() {
+        return mStartBatteryLevel;
+    }
+
+    int getEndBatteryLevel() {
+        return mEndBatteryLevel;
+    }
+
+    long getScreenOnTime() {
         return mScreenOnTime;
     }
 
-    public List<BatteryDiffEntry> getAppDiffEntryList() {
+    List<BatteryDiffEntry> getAppDiffEntryList() {
         return mAppEntries;
     }
 
-    public List<BatteryDiffEntry> getSystemDiffEntryList() {
+    List<BatteryDiffEntry> getSystemDiffEntryList() {
         return mSystemEntries;
     }
 
+    @Override
+    public String toString() {
+        return new StringBuilder("BatteryDiffData{")
+                .append("startTimestamp:" + utcToLocalTimeForLogging(mStartTimestamp))
+                .append("|endTimestamp:" + utcToLocalTimeForLogging(mEndTimestamp))
+                .append("|startLevel:" + mStartBatteryLevel)
+                .append("|endLevel:" + mEndBatteryLevel)
+                .append("|screenOnTime:" + mScreenOnTime)
+                .append("|appEntries.size:" + mAppEntries.size())
+                .append("|systemEntries.size:" + mSystemEntries.size())
+                .append("}")
+                .toString();
+    }
+
     /** Removes fake usage data and hidden packages. */
     private void purgeBatteryDiffData(final PowerUsageFeatureProvider featureProvider) {
         purgeBatteryDiffData(featureProvider, mAppEntries);
@@ -109,7 +153,7 @@
             final long screenOnTimeInMs = entry.mScreenOnTimeInMs;
             final double comsumePower = entry.mConsumePower;
             final String packageName = entry.getPackageName();
-            final Integer componentId = entry.mBatteryHistEntry.mDrainType;
+            final Integer componentId = entry.mComponentId;
             if ((screenOnTimeInMs < screenOnTimeThresholdInMs
                     && comsumePower < consumePowerThreshold)
                     || ConvertUtils.FAKE_PACKAGE_NAME.equals(packageName)
@@ -130,14 +174,16 @@
             final @NonNull Set<Integer> systemAppsUids,
             final @NonNull List<BatteryDiffEntry> appEntries) {
         final List<String> systemAppsAllowlist = featureProvider.getSystemAppsAllowlist();
-        BatteryDiffEntry.SystemAppsBatteryDiffEntry systemAppsDiffEntry = null;
+        BatteryDiffEntry systemAppsDiffEntry = null;
         final Iterator<BatteryDiffEntry> appListIterator = appEntries.iterator();
         while (appListIterator.hasNext()) {
             final BatteryDiffEntry batteryDiffEntry = appListIterator.next();
             if (needsCombineInSystemApp(batteryDiffEntry, systemAppsAllowlist,
                     systemAppsPackageNames, systemAppsUids)) {
                 if (systemAppsDiffEntry == null) {
-                    systemAppsDiffEntry = new BatteryDiffEntry.SystemAppsBatteryDiffEntry(context);
+                    systemAppsDiffEntry = new BatteryDiffEntry(context,
+                            BatteryDiffEntry.SYSTEM_APPS_KEY, BatteryDiffEntry.SYSTEM_APPS_KEY,
+                            ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
                 }
                 systemAppsDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
                 systemAppsDiffEntry.mForegroundUsageTimeInMs +=
@@ -159,17 +205,18 @@
         final Set<Integer> othersSystemComponentSet = featureProvider.getOthersSystemComponentSet();
         final Set<String> othersCustomComponentNameSet =
                 featureProvider.getOthersCustomComponentNameSet();
-        BatteryDiffEntry.OthersBatteryDiffEntry othersDiffEntry = null;
+        BatteryDiffEntry othersDiffEntry = null;
         final Iterator<BatteryDiffEntry> systemListIterator = systemEntries.iterator();
         while (systemListIterator.hasNext()) {
             final BatteryDiffEntry batteryDiffEntry = systemListIterator.next();
-            final int componentId = batteryDiffEntry.mBatteryHistEntry.mDrainType;
+            final int componentId = batteryDiffEntry.mComponentId;
             if (othersSystemComponentSet.contains(componentId) || (
                     componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
                             && othersCustomComponentNameSet.contains(
                                     batteryDiffEntry.getAppLabel()))) {
                 if (othersDiffEntry == null) {
-                    othersDiffEntry = new BatteryDiffEntry.OthersBatteryDiffEntry(context);
+                    othersDiffEntry = new BatteryDiffEntry(context, BatteryDiffEntry.OTHERS_KEY,
+                            BatteryDiffEntry.OTHERS_KEY, ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
                 }
                 othersDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
                 othersDiffEntry.setTotalConsumePower(
@@ -188,7 +235,7 @@
             final @NonNull List<String> systemAppsAllowlist,
             final @NonNull Set<String> systemAppsPackageNames,
             final @NonNull Set<Integer> systemAppsUids) {
-        if (batteryDiffEntry.mBatteryHistEntry.mIsHidden) {
+        if (batteryDiffEntry.mIsHidden) {
             return true;
         }
 
@@ -201,7 +248,7 @@
             return true;
         }
 
-        int uid = (int) batteryDiffEntry.mBatteryHistEntry.mUid;
+        int uid = (int) batteryDiffEntry.mUid;
         return systemAppsPackageNames.contains(packageName) || systemAppsUids.contains(uid);
     }
 
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
index 2ed9196..b284ea5 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
@@ -15,7 +15,6 @@
  */
 package com.android.settings.fuelgauge.batteryusage;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -24,6 +23,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -45,12 +45,29 @@
     static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();
     // Whether a specific item is valid to launch restriction page?
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    public static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
-
+    static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
     /** A comparator for {@link BatteryDiffEntry} based on the sorting key. */
-    public static final Comparator<BatteryDiffEntry> COMPARATOR =
+    static final Comparator<BatteryDiffEntry> COMPARATOR =
             (a, b) -> Double.compare(b.getSortingKey(), a.getSortingKey());
+    static final String SYSTEM_APPS_KEY = "A|SystemApps";
+    static final String OTHERS_KEY = "S|Others";
 
+    // key -> (label_id, icon_id)
+    private static final Map<String, Pair<Integer, Integer>> SPECIAL_ENTRY_MAP = Map.of(
+            SYSTEM_APPS_KEY,
+            Pair.create(R.string.battery_usage_system_apps, R.drawable.ic_power_system),
+            OTHERS_KEY,
+            Pair.create(R.string.battery_usage_others,
+                    R.drawable.ic_settings_battery_usage_others));
+
+    public long mUid;
+    public long mUserId;
+    public String mKey;
+    public boolean mIsHidden;
+    public int mComponentId;
+    public String mLegacyPackageName;
+    public String mLegacyLabel;
+    public int mConsumerType;
     public long mForegroundUsageTimeInMs;
     public long mBackgroundUsageTimeInMs;
     public long mScreenOnTimeInMs;
@@ -59,8 +76,6 @@
     public double mForegroundServiceUsageConsumePower;
     public double mBackgroundUsageConsumePower;
     public double mCachedUsageConsumePower;
-    // A BatteryHistEntry corresponding to this diff usage data.
-    public final BatteryHistEntry mBatteryHistEntry;
 
     protected Context mContext;
 
@@ -83,6 +98,14 @@
 
     public BatteryDiffEntry(
             Context context,
+            long uid,
+            long userId,
+            String key,
+            boolean isHidden,
+            int componentId,
+            String legacyPackageName,
+            String legacyLabel,
+            int consumerType,
             long foregroundUsageTimeInMs,
             long backgroundUsageTimeInMs,
             long screenOnTimeInMs,
@@ -90,21 +113,36 @@
             double foregroundUsageConsumePower,
             double foregroundServiceUsageConsumePower,
             double backgroundUsageConsumePower,
-            double cachedUsageConsumePower,
-            BatteryHistEntry batteryHistEntry) {
+            double cachedUsageConsumePower) {
         mContext = context;
+        mUid = uid;
+        mUserId = userId;
+        mKey = key;
+        mIsHidden = isHidden;
+        mComponentId = componentId;
+        mLegacyPackageName = legacyPackageName;
+        mLegacyLabel = legacyLabel;
+        mConsumerType = consumerType;
+        mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
+        mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
+        mScreenOnTimeInMs = screenOnTimeInMs;
         mConsumePower = consumePower;
         mForegroundUsageConsumePower = foregroundUsageConsumePower;
         mForegroundServiceUsageConsumePower = foregroundServiceUsageConsumePower;
         mBackgroundUsageConsumePower = backgroundUsageConsumePower;
         mCachedUsageConsumePower = cachedUsageConsumePower;
-        mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
-        mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
-        mScreenOnTimeInMs = screenOnTimeInMs;
-        mBatteryHistEntry = batteryHistEntry;
         mUserManager = context.getSystemService(UserManager.class);
     }
 
+    public BatteryDiffEntry(Context context, String key, String legacyLabel, int consumerType) {
+        this(context, /*uid=*/ 0, /*userId=*/ 0, key, /*isHidden=*/ false, /*componentId=*/ -1,
+                /*legacyPackageName=*/ null, legacyLabel, consumerType,
+                /*foregroundUsageTimeInMs=*/ 0, /*backgroundUsageTimeInMs=*/ 0,
+                /*screenOnTimeInMs=*/ 0, /*consumePower=*/ 0, /*foregroundUsageConsumePower=*/ 0,
+                /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0,
+                /*cachedUsageConsumePower=*/ 0);
+    }
+
     /** Sets the total consumed power in a specific time slot. */
     public void setTotalConsumePower(double totalConsumePower) {
         mTotalConsumePower = totalConsumePower;
@@ -135,13 +173,22 @@
 
     /** Gets the key for sorting */
     public double getSortingKey() {
-        return getPercentage() + getAdjustPercentageOffset();
+        return getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())
+                ? -1 : getPercentage() + getAdjustPercentageOffset();
     }
 
     /** Clones a new instance. */
     public BatteryDiffEntry clone() {
         return new BatteryDiffEntry(
                 this.mContext,
+                this.mUid,
+                this.mUserId,
+                this.mKey,
+                this.mIsHidden,
+                this.mComponentId,
+                this.mLegacyPackageName,
+                this.mLegacyLabel,
+                this.mConsumerType,
                 this.mForegroundUsageTimeInMs,
                 this.mBackgroundUsageTimeInMs,
                 this.mScreenOnTimeInMs,
@@ -149,17 +196,14 @@
                 this.mForegroundUsageConsumePower,
                 this.mForegroundServiceUsageConsumePower,
                 this.mBackgroundUsageConsumePower,
-                this.mCachedUsageConsumePower,
-                this.mBatteryHistEntry /*same instance*/);
+                this.mCachedUsageConsumePower);
     }
 
     /** Gets the app label name for this entry. */
     public String getAppLabel() {
         loadLabelAndIcon();
-        // Returns default applicationn label if we cannot find it.
-        return mAppLabel == null || mAppLabel.length() == 0
-                ? mBatteryHistEntry.mAppLabel
-                : mAppLabel;
+        // Returns default application label if we cannot find it.
+        return mAppLabel == null || mAppLabel.length() == 0 ? mLegacyLabel : mAppLabel;
     }
 
     /** Gets the app icon {@link Drawable} for this entry. */
@@ -179,7 +223,7 @@
     /** Gets the searching package name for UID battery type. */
     public String getPackageName() {
         final String packageName = mDefaultPackageName != null
-                ? mDefaultPackageName : mBatteryHistEntry.mPackageName;
+                ? mDefaultPackageName : mLegacyPackageName;
         if (packageName == null) {
             return packageName;
         }
@@ -198,10 +242,10 @@
 
     /** Whether the current BatteryDiffEntry is system component or not. */
     public boolean isSystemEntry() {
-        if (mBatteryHistEntry.mIsHidden) {
+        if (mIsHidden) {
             return false;
         }
-        switch (mBatteryHistEntry.mConsumerType) {
+        switch (mConsumerType) {
             case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
             case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
                 return true;
@@ -236,12 +280,22 @@
         updateRestrictionFlagState();
         sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction));
 
+        if (getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())) {
+            Pair<Integer, Integer> pair = SPECIAL_ENTRY_MAP.get(getKey());
+            mAppLabel = mContext.getString(pair.first);
+            mAppIconId = pair.second;
+            mAppIcon = mContext.getDrawable(mAppIconId);
+            sResourceCache.put(
+                    getKey(),
+                    new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
+            return;
+        }
+
         // Loads application icon and label based on consumer type.
-        switch (mBatteryHistEntry.mConsumerType) {
+        switch (mConsumerType) {
             case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
                 final BatteryEntry.NameAndIcon nameAndIconForUser =
-                        BatteryEntry.getNameAndIconFromUserId(
-                                mContext, (int) mBatteryHistEntry.mUserId);
+                        BatteryEntry.getNameAndIconFromUserId(mContext, (int) mUserId);
                 if (nameAndIconForUser != null) {
                     mAppIcon = nameAndIconForUser.mIcon;
                     mAppLabel = nameAndIconForUser.mName;
@@ -252,8 +306,7 @@
                 break;
             case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
                 final BatteryEntry.NameAndIcon nameAndIconForSystem =
-                        BatteryEntry.getNameAndIconFromPowerComponent(
-                                mContext, mBatteryHistEntry.mDrainType);
+                        BatteryEntry.getNameAndIconFromPowerComponent(mContext, mComponentId);
                 if (nameAndIconForSystem != null) {
                     mAppLabel = nameAndIconForSystem.mName;
                     if (nameAndIconForSystem.mIconId != 0) {
@@ -283,12 +336,12 @@
     }
 
     String getKey() {
-        return mBatteryHistEntry.getKey();
+        return mKey;
     }
 
     @VisibleForTesting
     void updateRestrictionFlagState() {
-        if (!mBatteryHistEntry.isAppEntry()) {
+        if (isSystemEntry()) {
             mValidForRestriction = false;
             return;
         }
@@ -348,7 +401,7 @@
             return;
         }
 
-        final int uid = (int) mBatteryHistEntry.mUid;
+        final int uid = (int) mUid;
         final String[] packages = packageManager.getPackagesForUid(uid);
         // Loads special defined application label and icon if available.
         if (packages == null || packages.length == 0) {
@@ -394,8 +447,7 @@
                         StringUtil.formatElapsedTime(mContext, (double) mScreenOnTimeInMs,
                                 /*withSeconds=*/ true, /*collapseTimeUnit=*/ false)))
                 .append(String.format("\n\tpackage:%s|%s uid:%d userId:%d",
-                        mBatteryHistEntry.mPackageName, getPackageName(),
-                        mBatteryHistEntry.mUid, mBatteryHistEntry.mUserId));
+                        mLegacyPackageName, getPackageName(), mUid, mUserId));
         return builder.toString();
     }
 
@@ -406,130 +458,8 @@
     }
 
     private Drawable getBadgeIconForUser(Drawable icon) {
-        final int userId = UserHandle.getUserId((int) mBatteryHistEntry.mUid);
+        final int userId = UserHandle.getUserId((int) mUid);
         return userId == UserHandle.USER_OWNER ? icon :
                 mUserManager.getBadgedIconForUser(icon, new UserHandle(userId));
     }
-
-    /** Specific battery diff entry for system apps. */
-    static class SystemAppsBatteryDiffEntry extends BatteryDiffEntry {
-        SystemAppsBatteryDiffEntry(Context context) {
-            super(context,
-                    /*foregroundUsageTimeInMs=*/ 0,
-                    /*backgroundUsageTimeInMs=*/ 0,
-                    /*screenOnTimeInMs=*/ 0,
-                    /*consumePower=*/ 0,
-                    /*foregroundUsageConsumePower=*/ 0,
-                    /*foregroundServiceUsageConsumePower=*/ 0,
-                    /*backgroundUsageConsumePower=*/ 0,
-                    /*cachedUsageConsumePower=*/ 0,
-                    new BatteryHistEntry(new ContentValues()));
-        }
-
-        @Override
-        public String getKey() {
-            return "A|SystemApps";
-        }
-
-        @Override
-        public String getAppLabel() {
-            return mContext.getString(R.string.battery_usage_system_apps);
-        }
-
-        @Override
-        public Drawable getAppIcon() {
-            return mContext.getDrawable(R.drawable.ic_power_system);
-        }
-
-        @Override
-        public boolean validForRestriction() {
-            return false;
-        }
-
-        @Override
-        public boolean isSystemEntry() {
-            return false;
-        }
-
-        @Override
-        public double getSortingKey() {
-            // Always on the bottom of the app list.
-            return -1;
-        }
-
-        @Override
-        public BatteryDiffEntry clone() {
-            SystemAppsBatteryDiffEntry newEntry = new SystemAppsBatteryDiffEntry(this.mContext);
-            newEntry.mForegroundUsageTimeInMs = this.mForegroundUsageTimeInMs;
-            newEntry.mBackgroundUsageTimeInMs = this.mBackgroundUsageTimeInMs;
-            newEntry.mScreenOnTimeInMs = this.mScreenOnTimeInMs;
-            newEntry.mConsumePower = this.mConsumePower;
-            newEntry.mForegroundUsageConsumePower = this.mForegroundUsageConsumePower;
-            newEntry.mForegroundServiceUsageConsumePower = this.mForegroundServiceUsageConsumePower;
-            newEntry.mBackgroundUsageConsumePower = this.mBackgroundUsageConsumePower;
-            newEntry.mCachedUsageConsumePower = this.mCachedUsageConsumePower;
-            return newEntry;
-        }
-    }
-
-    /** Specific battery diff entry for others. */
-    static class OthersBatteryDiffEntry extends BatteryDiffEntry {
-        OthersBatteryDiffEntry(Context context) {
-            super(context,
-                    /*foregroundUsageTimeInMs=*/ 0,
-                    /*backgroundUsageTimeInMs=*/ 0,
-                    /*screenOnTimeInMs=*/ 0,
-                    /*consumePower=*/ 0,
-                    /*foregroundUsageConsumePower=*/ 0,
-                    /*foregroundServiceUsageConsumePower=*/ 0,
-                    /*backgroundUsageConsumePower=*/ 0,
-                    /*cachedUsageConsumePower=*/ 0,
-                    new BatteryHistEntry(new ContentValues()));
-        }
-
-        @Override
-        public String getKey() {
-            return "S|Others";
-        }
-
-        @Override
-        public String getAppLabel() {
-            return mContext.getString(R.string.battery_usage_others);
-        }
-
-        @Override
-        public Drawable getAppIcon() {
-            return mContext.getDrawable(R.drawable.ic_settings_battery_usage_others);
-        }
-
-        @Override
-        public boolean validForRestriction() {
-            return false;
-        }
-
-        @Override
-        public boolean isSystemEntry() {
-            return true;
-        }
-
-        @Override
-        public double getSortingKey() {
-            // Always on the bottom of the system list.
-            return -1;
-        }
-
-        @Override
-        public BatteryDiffEntry clone() {
-            OthersBatteryDiffEntry newEntry = new OthersBatteryDiffEntry(this.mContext);
-            newEntry.mForegroundUsageTimeInMs = this.mForegroundUsageTimeInMs;
-            newEntry.mBackgroundUsageTimeInMs = this.mBackgroundUsageTimeInMs;
-            newEntry.mScreenOnTimeInMs = this.mScreenOnTimeInMs;
-            newEntry.mConsumePower = this.mConsumePower;
-            newEntry.mForegroundUsageConsumePower = this.mForegroundUsageConsumePower;
-            newEntry.mForegroundServiceUsageConsumePower = this.mForegroundServiceUsageConsumePower;
-            newEntry.mBackgroundUsageConsumePower = this.mBackgroundUsageConsumePower;
-            newEntry.mCachedUsageConsumePower = this.mCachedUsageConsumePower;
-            return newEntry;
-        }
-    }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java
index 7f86b7c..86538ee 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryEntry.java
@@ -252,33 +252,6 @@
         return mPowerComponentId;
     }
 
-    void getQuickNameIconForUid(
-            final int uid, final String[] packages, final boolean loadDataInBackground) {
-        // Locale sync to system config in Settings
-        final Locale locale = Locale.getDefault();
-        if (sCurrentLocale != locale) {
-            clearUidCache();
-            sCurrentLocale = locale;
-        }
-
-        final String uidString = Integer.toString(uid);
-        if (sUidCache.containsKey(uidString)) {
-            UidToDetail utd = sUidCache.get(uidString);
-            mDefaultPackageName = utd.mPackageName;
-            mName = utd.mName;
-            mIcon = utd.mIcon;
-            return;
-        }
-
-        if (packages == null || packages.length == 0) {
-            final NameAndIcon nameAndIcon = getNameAndIconFromUid(mContext, mName, uid);
-            mIcon = nameAndIcon.mIcon;
-            mName = nameAndIcon.mName;
-        } else {
-            mIcon = mContext.getPackageManager().getDefaultActivityIcon();
-        }
-    }
-
     /** Loads the app label and icon image and stores into the cache. */
     public static NameAndIcon loadNameAndIcon(
             Context context,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java
index 827f0fc..6f78566 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntry.java
@@ -169,21 +169,6 @@
         return mIsValidEntry;
     }
 
-    /** Whether this {@link BatteryHistEntry} is user consumer or not. */
-    public boolean isUserEntry() {
-        return mConsumerType == ConvertUtils.CONSUMER_TYPE_USER_BATTERY;
-    }
-
-    /** Whether this {@link BatteryHistEntry} is app consumer or not. */
-    public boolean isAppEntry() {
-        return mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY;
-    }
-
-    /** Whether this {@link BatteryHistEntry} is system consumer or not. */
-    public boolean isSystemEntry() {
-        return mConsumerType == ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY;
-    }
-
     /** Gets an identifier to represent this {@link BatteryHistEntry}. */
     public String getKey() {
         if (mKey == null) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java
deleted file mode 100644
index 9a0e410..0000000
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java
+++ /dev/null
@@ -1,45 +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.fuelgauge.batteryusage;
-
-import android.content.Context;
-
-import com.android.settingslib.utils.AsyncLoaderCompat;
-
-import java.util.Calendar;
-import java.util.Map;
-
-/** Loader that can be used to load battery history information. */
-public class BatteryHistoryLoader
-        extends AsyncLoaderCompat<Map<Long, Map<String, BatteryHistEntry>>> {
-    private static final String TAG = "BatteryHistoryLoader";
-
-    private final Context mContext;
-
-    public BatteryHistoryLoader(Context context) {
-        super(context);
-        mContext = context;
-    }
-
-    @Override
-    protected void onDiscardResult(Map<Long, Map<String, BatteryHistEntry>> result) {
-    }
-
-    @Override
-    public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() {
-        return DatabaseUtils.getHistoryMapSinceLastFullCharge(mContext, Calendar.getInstance());
-    }
-}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java
index c78b3c7..d64bf34 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java
@@ -17,17 +17,13 @@
 package com.android.settings.fuelgauge.batteryusage;
 
 import android.content.Context;
-import android.os.BatteryUsageStats;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryInfo;
 import com.android.settings.fuelgauge.BatteryUtils;
 
 /**
@@ -36,9 +32,6 @@
 public class BatteryHistoryPreference extends Preference {
     private static final String TAG = "BatteryHistoryPreference";
 
-    @VisibleForTesting
-    BatteryInfo mBatteryInfo;
-
     private BatteryChartView mDailyChartView;
     private BatteryChartView mHourlyChartView;
     private BatteryChartPreferenceController mChartPreferenceController;
@@ -49,13 +42,6 @@
         setSelectable(false);
     }
 
-    void setBatteryUsageStats(@NonNull BatteryUsageStats batteryUsageStats) {
-        BatteryInfo.getBatteryInfo(getContext(), info -> {
-            mBatteryInfo = info;
-            notifyChanged();
-        }, batteryUsageStats, false);
-    }
-
     void setChartPreferenceController(BatteryChartPreferenceController controller) {
         mChartPreferenceController = controller;
         if (mDailyChartView != null && mHourlyChartView != null) {
@@ -67,9 +53,6 @@
     public void onBindViewHolder(PreferenceViewHolder view) {
         super.onBindViewHolder(view);
         final long startTime = System.currentTimeMillis();
-        if (mBatteryInfo == null) {
-            return;
-        }
         final TextView companionTextView = (TextView) view.findViewById(R.id.companion_text);
         mDailyChartView = (BatteryChartView) view.findViewById(R.id.daily_battery_chart);
         mDailyChartView.setCompanionTextView(companionTextView);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java
index 4ff9eeb..53ebbd9 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java
@@ -16,15 +16,28 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
+
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
 /** Wraps the battery timestamp and level data used for battery usage chart. */
 public final class BatteryLevelData {
+    private static final long MIN_SIZE = 2;
+    private static final long TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2;
+
     /** A container for the battery timestamp and level data. */
     public static final class PeriodBatteryLevelData {
         // The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when
@@ -33,12 +46,14 @@
         private final List<Integer> mLevels;
 
         public PeriodBatteryLevelData(
-                @NonNull List<Long> timestamps, @NonNull List<Integer> levels) {
-            Preconditions.checkArgument(timestamps.size() == levels.size(),
-                    /* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: "
-                            + levels.size());
+                @NonNull Map<Long, Integer> batteryLevelMap,
+                @NonNull List<Long> timestamps) {
             mTimestamps = timestamps;
-            mLevels = levels;
+            mLevels = new ArrayList<>(timestamps.size());
+            for (Long timestamp : timestamps) {
+                mLevels.add(batteryLevelMap.containsKey(timestamp)
+                        ? batteryLevelMap.get(timestamp) : BATTERY_LEVEL_UNKNOWN);
+            }
         }
 
         public List<Long> getTimestamps() {
@@ -68,15 +83,21 @@
     // The size of hourly data must be the size of daily data - 1.
     private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
 
-    public BatteryLevelData(
-            @NonNull PeriodBatteryLevelData dailyBatteryLevels,
-            @NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
-        final long dailySize = dailyBatteryLevels.getTimestamps().size();
-        final long hourlySize = hourlyBatteryLevelsPerDay.size();
-        Preconditions.checkArgument(hourlySize == dailySize - 1,
-                /* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize);
-        mDailyBatteryLevels = dailyBatteryLevels;
-        mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
+    public BatteryLevelData(@NonNull Map<Long, Integer> batteryLevelMap) {
+        final int mapSize = batteryLevelMap.size();
+        Preconditions.checkArgument(mapSize >= MIN_SIZE, "batteryLevelMap size:" + mapSize);
+
+        final List<Long> timestampList = new ArrayList<>(batteryLevelMap.keySet());
+        Collections.sort(timestampList);
+        final List<Long> dailyTimestamps = getDailyTimestamps(timestampList);
+        final List<List<Long>> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps);
+
+        mDailyBatteryLevels = new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps);
+        mHourlyBatteryLevelsPerDay = new ArrayList<>(hourlyTimestamps.size());
+        for (List<Long> hourlyTimestampsPerDay : hourlyTimestamps) {
+            mHourlyBatteryLevelsPerDay.add(
+                    new PeriodBatteryLevelData(batteryLevelMap, hourlyTimestampsPerDay));
+        }
     }
 
     public PeriodBatteryLevelData getDailyBatteryLevels() {
@@ -94,5 +115,69 @@
                 Objects.toString(mDailyBatteryLevels),
                 Objects.toString(mHourlyBatteryLevelsPerDay));
     }
+
+    @Nullable
+    static BatteryLevelData combine(@Nullable BatteryLevelData existingBatteryLevelData,
+            List<BatteryEvent> batteryLevelRecordEvents) {
+        final Map<Long, Integer> batteryLevelMap = new ArrayMap<>(batteryLevelRecordEvents.size());
+        for (BatteryEvent event : batteryLevelRecordEvents) {
+            batteryLevelMap.put(event.getTimestamp(), event.getBatteryLevel());
+        }
+        if (existingBatteryLevelData != null) {
+            List<PeriodBatteryLevelData> multiDaysData =
+                    existingBatteryLevelData.getHourlyBatteryLevelsPerDay();
+            for (int dayIndex = 0; dayIndex < multiDaysData.size(); dayIndex++) {
+                PeriodBatteryLevelData oneDayData = multiDaysData.get(dayIndex);
+                for (int hourIndex = 0; hourIndex < oneDayData.getLevels().size(); hourIndex++) {
+                    batteryLevelMap.put(oneDayData.getTimestamps().get(hourIndex),
+                            oneDayData.getLevels().get(hourIndex));
+                }
+            }
+        }
+        return batteryLevelMap.size() < MIN_SIZE ? null : new BatteryLevelData(batteryLevelMap);
+    }
+
+    /**
+     * Computes expected daily timestamp slots.
+     *
+     * The valid result should be composed of 3 parts:
+     * 1) start timestamp
+     * 2) every 00:00 timestamp (default timezone) between the start and end
+     * 3) end timestamp
+     * Otherwise, returns an empty list.
+     */
+    @VisibleForTesting
+    static List<Long> getDailyTimestamps(final List<Long> timestampList) {
+        Preconditions.checkArgument(
+                timestampList.size() >= MIN_SIZE, "timestampList size:" + timestampList.size());
+        final List<Long> dailyTimestampList = new ArrayList<>();
+        final long startTimestamp = timestampList.get(0);
+        final long endTimestamp = timestampList.get(timestampList.size() - 1);
+        for (long timestamp = startTimestamp; timestamp < endTimestamp;
+                timestamp = TimestampUtils.getNextDayTimestamp(timestamp)) {
+            dailyTimestampList.add(timestamp);
+        }
+        dailyTimestampList.add(endTimestamp);
+        return dailyTimestampList;
+    }
+
+    private static List<List<Long>> getHourlyTimestamps(final List<Long> dailyTimestamps) {
+        final List<List<Long>> hourlyTimestamps = new ArrayList<>();
+        for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) {
+            final List<Long> hourlyTimestampsPerDay = new ArrayList<>();
+            final long startTime = dailyTimestamps.get(dailyIndex);
+            final long endTime = dailyTimestamps.get(dailyIndex + 1);
+
+            hourlyTimestampsPerDay.add(startTime);
+            for (long timestamp = TimestampUtils.getNextEvenHourTimestamp(startTime);
+                    timestamp < endTime; timestamp += TIME_SLOT) {
+                hourlyTimestampsPerDay.add(timestamp);
+            }
+            hourlyTimestampsPerDay.add(endTime);
+
+            hourlyTimestamps.add(hourlyTimestampsPerDay);
+        }
+        return hourlyTimestamps;
+    }
 }
 
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
new file mode 100644
index 0000000..5857d62
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreference.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+import com.google.android.material.button.MaterialButton;
+
+/**
+ * A preference for displaying the battery tips card view.
+ */
+public class BatteryTipsCardPreference extends Preference implements View.OnClickListener {
+
+    private static final String TAG = "BatteryTipsCardPreference";
+
+    private final PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+    @VisibleForTesting
+    CharSequence mMainButtonLabel;
+    @VisibleForTesting
+    CharSequence mDismissButtonLabel;
+    @VisibleForTesting
+    String mDestinationComponentName;
+    @VisibleForTesting
+    int mSourceMetricsCategory;
+
+    public BatteryTipsCardPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setLayoutResource(R.layout.battery_tips_card);
+        setSelectable(false);
+        mPowerUsageFeatureProvider = FeatureFactory.getFactory(context)
+                .getPowerUsageFeatureProvider(context);
+    }
+
+    /**
+     * Update the label of main button in tips card.
+     */
+    public void setMainButtonLabel(CharSequence label) {
+        if (!TextUtils.equals(mMainButtonLabel, label)) {
+            mMainButtonLabel = label;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Update the label of dismiss button in tips card.
+     */
+    public void setDismissButtonLabel(CharSequence label) {
+        if (!TextUtils.equals(mDismissButtonLabel, label)) {
+            mDismissButtonLabel = label;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Update the info of target fragment launched by main button.
+     */
+    public void setMainButtonLauncherInfo(final String destinationClassName,
+            final Integer sourceMetricsCategory) {
+        mDestinationComponentName = destinationClassName;
+        mSourceMetricsCategory = sourceMetricsCategory;
+    }
+
+    @Override
+    public void onClick(View view) {
+        final int viewId = view.getId();
+        if (viewId == R.id.main_button || viewId == R.id.tips_card) {
+            if (TextUtils.isEmpty(mDestinationComponentName)) {
+                return;
+            }
+            new SubSettingLauncher(getContext())
+                    .setDestination(mDestinationComponentName)
+                    .setSourceMetricsCategory(mSourceMetricsCategory)
+                    .launch();
+            setVisible(false);
+        } else if (viewId == R.id.dismiss_button) {
+            setVisible(false);
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        ((TextView) view.findViewById(R.id.title)).setText(getTitle());
+
+        LinearLayout tipsCard = (LinearLayout) view.findViewById(R.id.tips_card);
+        tipsCard.setOnClickListener(this);
+        MaterialButton mainButton = (MaterialButton) view.findViewById(R.id.main_button);
+        mainButton.setOnClickListener(this);
+        mainButton.setText(mMainButtonLabel);
+        MaterialButton dismissButton = (MaterialButton) view.findViewById(R.id.dismiss_button);
+        dismissButton.setOnClickListener(this);
+        dismissButton.setText(mDismissButtonLabel);
+
+        if (!mPowerUsageFeatureProvider.isBatteryTipsFeedbackEnabled()) {
+            return;
+        }
+        view.findViewById(R.id.tips_card)
+                .setBackgroundResource(R.drawable.battery_tips_half_rounded_top_bg);
+        view.findViewById(R.id.feedback_card).setVisibility(View.VISIBLE);
+
+        ImageButton thumbUpButton = (ImageButton) view.findViewById(R.id.thumb_up);
+        thumbUpButton.setOnClickListener(this);
+        ImageButton thumbDownButton = (ImageButton) view.findViewById(R.id.thumb_down);
+        thumbDownButton.setOnClickListener(this);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
new file mode 100644
index 0000000..42604f5
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.function.Function;
+
+/** Controls the update for battery tips card */
+public class BatteryTipsController extends BasePreferenceController {
+
+    private static final String TAG = "BatteryTipsController";
+    private static final String ROOT_PREFERENCE_KEY = "battery_tips_category";
+    private static final String CARD_PREFERENCE_KEY = "battery_tips_card";
+
+    @VisibleForTesting
+    BatteryTipsCardPreference mCardPreference;
+    @VisibleForTesting
+    PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+    public BatteryTipsController(Context context) {
+        super(context, ROOT_PREFERENCE_KEY);
+        mPowerUsageFeatureProvider = FeatureFactory.getFactory(context)
+                .getPowerUsageFeatureProvider(context);
+    }
+
+    private boolean isTipsCardVisible() {
+        // TODO: compared with the timestamp of last user dismiss action in sharedPreference.
+        return mPowerUsageFeatureProvider.isBatteryTipsEnabled();
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mCardPreference = screen.findPreference(CARD_PREFERENCE_KEY);
+    }
+
+    private <T> T getInfo(PowerAnomalyEvent powerAnomalyEvent,
+                          Function<WarningBannerInfo, T> warningBannerInfoSupplier,
+                          Function<WarningItemInfo, T> warningItemInfoSupplier) {
+        if (powerAnomalyEvent.hasWarningBannerInfo() && warningBannerInfoSupplier != null) {
+            return warningBannerInfoSupplier.apply(powerAnomalyEvent.getWarningBannerInfo());
+        } else if (powerAnomalyEvent.hasWarningItemInfo() && warningItemInfoSupplier != null) {
+            return warningItemInfoSupplier.apply(powerAnomalyEvent.getWarningItemInfo());
+        }
+        return null;
+    }
+
+    private String getString(PowerAnomalyEvent powerAnomalyEvent,
+                             Function<WarningBannerInfo, String> warningBannerInfoSupplier,
+                             Function<WarningItemInfo, String> warningItemInfoSupplier,
+                             int resourceId, int resourceIndex) {
+        String string =
+                getInfo(powerAnomalyEvent, warningBannerInfoSupplier, warningItemInfoSupplier);
+
+        if (!TextUtils.isEmpty(string) || resourceId < 0) {
+            return string;
+        }
+
+        String[] stringArray = mContext.getResources().getStringArray(resourceId);
+        if (resourceIndex >= 0 && resourceIndex < stringArray.length) {
+            string = stringArray[resourceIndex];
+        }
+
+        return string;
+    }
+
+    @VisibleForTesting
+    void handleBatteryTipsCardUpdated(PowerAnomalyEvent powerAnomalyEvent) {
+        if (!isTipsCardVisible()) {
+            mCardPreference.setVisible(false);
+            return;
+        }
+        if (powerAnomalyEvent == null) {
+            mCardPreference.setVisible(false);
+            return;
+        }
+
+        // Get card preference strings and navigate fragment info
+        final int resourceIndex = powerAnomalyEvent.hasKey()
+                ? powerAnomalyEvent.getKey().getNumber() : -1;
+
+        String titleString = getString(powerAnomalyEvent, WarningBannerInfo::getTitleString,
+                WarningItemInfo::getTitleString, R.array.power_anomaly_titles, resourceIndex);
+        if (titleString.isEmpty()) {
+            mCardPreference.setVisible(false);
+            return;
+        }
+
+        String mainBtnString = getString(powerAnomalyEvent,
+                WarningBannerInfo::getMainButtonString, WarningItemInfo::getMainButtonString,
+                R.array.power_anomaly_main_btn_strings, resourceIndex);
+        String dismissBtnString = getString(powerAnomalyEvent,
+                WarningBannerInfo::getCancelButtonString, WarningItemInfo::getCancelButtonString,
+                R.array.power_anomaly_dismiss_btn_strings, resourceIndex);
+
+        String destinationClassName = getString(powerAnomalyEvent,
+                WarningBannerInfo::getMainButtonDestination,
+                WarningItemInfo::getMainButtonDestination,
+                -1, -1);
+        Integer sourceMetricsCategory = getInfo(powerAnomalyEvent,
+                WarningBannerInfo::getMainButtonSourceMetricsCategory,
+                WarningItemInfo::getMainButtonSourceMetricsCategory);
+
+        // Updated card preference and main button fragment launcher
+        mCardPreference.setTitle(titleString);
+        mCardPreference.setMainButtonLabel(mainBtnString);
+        mCardPreference.setDismissButtonLabel(dismissBtnString);
+        mCardPreference.setMainButtonLauncherInfo(destinationClassName, sourceMetricsCategory);
+        mCardPreference.setVisible(true);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
index b262dee..8aa31e2 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
@@ -144,19 +144,17 @@
         }
         final PowerGaugePreference powerPref = (PowerGaugePreference) preference;
         final BatteryDiffEntry diffEntry = powerPref.getBatteryDiffEntry();
-        final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
-        final String packageName = histEntry.mPackageName;
-        final boolean isAppEntry = histEntry.isAppEntry();
+        final String packageName = diffEntry.getPackageName();
         mMetricsFeatureProvider.action(
                 /* attribution */ SettingsEnums.OPEN_BATTERY_USAGE,
-                /* action */ isAppEntry
-                        ? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM
-                        : SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM,
+                /* action */ diffEntry.isSystemEntry()
+                        ? SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM
+                        : SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM,
                 /* pageId */ SettingsEnums.OPEN_BATTERY_USAGE,
                 TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName,
                 (int) Math.round(diffEntry.getPercentage()));
         Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s",
-                diffEntry.getAppLabel(), histEntry.getKey(), histEntry.mPackageName));
+                diffEntry.getAppLabel(), diffEntry.getKey(), packageName));
         AdvancedPowerUsageDetail.startBatteryDetailPage(
                 mActivity, mFragment, diffEntry, powerPref.getPercentage(), mSlotTimestamp);
         return true;
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
index 1b2d4cd..edba7c4 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
@@ -21,7 +21,6 @@
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -36,12 +35,14 @@
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotDao;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
 
 import java.time.Clock;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Collectors;
 
 /** {@link ContentProvider} class to fetch battery usage data. */
 public class BatteryUsageContentProvider extends ContentProvider {
@@ -55,7 +56,12 @@
     private static final int APP_USAGE_LATEST_TIMESTAMP_CODE = 2;
     private static final int APP_USAGE_EVENT_CODE = 3;
     private static final int BATTERY_EVENT_CODE = 4;
+    private static final int LAST_FULL_CHARGE_TIMESTAMP_CODE = 5;
+    private static final int BATTERY_STATE_LATEST_TIMESTAMP_CODE = 6;
+    private static final int BATTERY_USAGE_SLOT_CODE = 7;
 
+    private static final List<Integer> ALL_BATTERY_EVENT_TYPES =
+            Arrays.stream(BatteryEventType.values()).map(type -> type.getNumber()).toList();
     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
     static {
@@ -75,12 +81,25 @@
                 DatabaseUtils.AUTHORITY,
                 /*path=*/ DatabaseUtils.BATTERY_EVENT_TABLE,
                 /*code=*/ BATTERY_EVENT_CODE);
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.LAST_FULL_CHARGE_TIMESTAMP_PATH,
+                /*code=*/ LAST_FULL_CHARGE_TIMESTAMP_CODE);
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.BATTERY_STATE_LATEST_TIMESTAMP_PATH,
+                /*code=*/ BATTERY_STATE_LATEST_TIMESTAMP_CODE);
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.BATTERY_USAGE_SLOT_TABLE,
+                /*code=*/ BATTERY_USAGE_SLOT_CODE);
     }
 
     private Clock mClock;
     private BatteryStateDao mBatteryStateDao;
     private AppUsageEventDao mAppUsageEventDao;
     private BatteryEventDao mBatteryEventDao;
+    private BatteryUsageSlotDao mBatteryUsageSlotDao;
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     public void setClock(Clock clock) {
@@ -94,9 +113,11 @@
             return false;
         }
         mClock = Clock.systemUTC();
-        mBatteryStateDao = BatteryStateDatabase.getInstance(getContext()).batteryStateDao();
-        mAppUsageEventDao = BatteryStateDatabase.getInstance(getContext()).appUsageEventDao();
-        mBatteryEventDao = BatteryStateDatabase.getInstance(getContext()).batteryEventDao();
+        final BatteryStateDatabase database = BatteryStateDatabase.getInstance(getContext());
+        mBatteryStateDao = database.batteryStateDao();
+        mAppUsageEventDao = database.appUsageEventDao();
+        mBatteryEventDao = database.batteryEventDao();
+        mBatteryUsageSlotDao = database.batteryUsageSlotDao();
         Log.w(TAG, "create content provider from " + getCallingPackage());
         return true;
     }
@@ -118,6 +139,12 @@
                 return getAppUsageLatestTimestamp(uri);
             case BATTERY_EVENT_CODE:
                 return getBatteryEvents(uri);
+            case LAST_FULL_CHARGE_TIMESTAMP_CODE:
+                return getLastFullChargeTimestamp(uri);
+            case BATTERY_STATE_LATEST_TIMESTAMP_CODE:
+                return getBatteryStateLatestTimestamp(uri);
+            case BATTERY_USAGE_SLOT_CODE:
+                return getBatteryUsageSlots(uri);
             default:
                 throw new IllegalArgumentException("unknown URI: " + uri);
         }
@@ -132,34 +159,31 @@
     @Nullable
     @Override
     public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
-        switch (sUriMatcher.match(uri)) {
-            case BATTERY_STATE_CODE:
-                try {
+        try {
+            switch (sUriMatcher.match(uri)) {
+                case BATTERY_STATE_CODE:
                     mBatteryStateDao.insert(BatteryState.create(contentValues));
-                    return uri;
-                } catch (RuntimeException e) {
-                    Log.e(TAG, "insert() from:" + uri + " error:" + e);
-                    return null;
-                }
-            case APP_USAGE_EVENT_CODE:
-                try {
+                    break;
+                case APP_USAGE_EVENT_CODE:
                     mAppUsageEventDao.insert(AppUsageEventEntity.create(contentValues));
-                    return uri;
-                } catch (RuntimeException e) {
-                    Log.e(TAG, "insert() from:" + uri + " error:" + e);
-                    return null;
-                }
-            case BATTERY_EVENT_CODE:
-                try {
+                    break;
+                case BATTERY_EVENT_CODE:
                     mBatteryEventDao.insert(BatteryEventEntity.create(contentValues));
-                    return uri;
-                } catch (RuntimeException e) {
-                    Log.e(TAG, "insert() from:" + uri + " error:" + e);
-                    return null;
-                }
-            default:
-                throw new IllegalArgumentException("unknown URI: " + uri);
+                    break;
+                case BATTERY_USAGE_SLOT_CODE:
+                    mBatteryUsageSlotDao.insert(BatteryUsageSlotEntity.create(contentValues));
+                    break;
+                default:
+                    throw new IllegalArgumentException("unknown URI: " + uri);
+            }
+        } catch (RuntimeException e) {
+            if (e instanceof  IllegalArgumentException) {
+                throw e;
+            }
+            Log.e(TAG, "insert() from:" + uri + " error:", e);
+            return null;
         }
+        return uri;
     }
 
     @Override
@@ -176,21 +200,44 @@
         throw new UnsupportedOperationException("unsupported!");
     }
 
-    private Cursor getBatteryStates(Uri uri) {
-        final long queryTimestamp = getQueryTimestamp(uri);
-        return getBatteryStates(uri, queryTimestamp);
-    }
-
-    private Cursor getBatteryStates(Uri uri, long firstTimestamp) {
+    private Cursor getLastFullChargeTimestamp(Uri uri) {
         final long timestamp = mClock.millis();
         Cursor cursor = null;
         try {
-            cursor = mBatteryStateDao.getCursorSinceLastFullCharge(firstTimestamp);
+            cursor = mBatteryEventDao.getLastFullChargeTimestamp();
         } catch (RuntimeException e) {
-            Log.e(TAG, "query() from:" + uri + " error:" + e);
+            Log.e(TAG, "query() from:" + uri + " error:", e);
         }
-        AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext()));
-        Log.d(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms");
+        Log.d(TAG, String.format("getLastFullChargeTimestamp() in %d/ms",
+                mClock.millis() - timestamp));
+        return cursor;
+    }
+
+    private Cursor getBatteryStateLatestTimestamp(Uri uri) {
+        final long queryTimestamp = getQueryTimestamp(uri);
+        final long timestamp = mClock.millis();
+        Cursor cursor = null;
+        try {
+            cursor = mBatteryStateDao.getLatestTimestampBefore(queryTimestamp);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "query() from:" + uri + " error:", e);
+        }
+        Log.d(TAG, String.format("getBatteryStateLatestTimestamp() no later than %d in %d/ms",
+                queryTimestamp, mClock.millis() - timestamp));
+        return cursor;
+    }
+
+    private Cursor getBatteryStates(Uri uri) {
+        final long queryTimestamp = getQueryTimestamp(uri);
+        final long timestamp = mClock.millis();
+        Cursor cursor = null;
+        try {
+            cursor = mBatteryStateDao.getBatteryStatesAfter(queryTimestamp);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "query() from:" + uri + " error:", e);
+        }
+        Log.d(TAG, String.format("getBatteryStates() after %d in %d/ms",
+                queryTimestamp, mClock.millis() - timestamp));
         return cursor;
     }
 
@@ -205,9 +252,9 @@
         try {
             cursor = mAppUsageEventDao.getAllForUsersAfter(queryUserIds, queryTimestamp);
         } catch (RuntimeException e) {
-            Log.e(TAG, "query() from:" + uri + " error:" + e);
+            Log.e(TAG, "query() from:" + uri + " error:", e);
         }
-        Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
+        Log.w(TAG, "getAppUsageEvents() in " + (mClock.millis() - timestamp) + "/ms");
         return cursor;
     }
 
@@ -221,42 +268,78 @@
         try {
             cursor = mAppUsageEventDao.getLatestTimestampOfUser(queryUserId);
         } catch (RuntimeException e) {
-            Log.e(TAG, "query() from:" + uri + " error:" + e);
+            Log.e(TAG, "query() from:" + uri + " error:", e);
         }
-        Log.d(TAG, String.format("query app usage latest timestamp %d for user %d in %d/ms",
-                timestamp, queryUserId, (mClock.millis() - timestamp)));
+        Log.d(TAG, String.format("getAppUsageLatestTimestamp() for user %d in %d/ms",
+                queryUserId, (mClock.millis() - timestamp)));
         return cursor;
     }
 
     private Cursor getBatteryEvents(Uri uri) {
+        List<Integer> queryBatteryEventTypes = getQueryBatteryEventTypes(uri);
+        if (queryBatteryEventTypes == null || queryBatteryEventTypes.isEmpty()) {
+            queryBatteryEventTypes = ALL_BATTERY_EVENT_TYPES;
+        }
         final long queryTimestamp = getQueryTimestamp(uri);
         final long timestamp = mClock.millis();
         Cursor cursor = null;
         try {
-            cursor = mBatteryEventDao.getAllAfter(queryTimestamp);
+            cursor = mBatteryEventDao.getAllAfter(queryTimestamp, queryBatteryEventTypes);
         } catch (RuntimeException e) {
-            Log.e(TAG, "query() from:" + uri + " error:" + e);
+            Log.e(TAG, "query() from:" + uri + " error:", e);
         }
-        Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
+        Log.w(TAG, "getBatteryEvents() in " + (mClock.millis() - timestamp) + "/ms");
         return cursor;
     }
 
+    private Cursor getBatteryUsageSlots(Uri uri) {
+        final long queryTimestamp = getQueryTimestamp(uri);
+        final long timestamp = mClock.millis();
+        Cursor cursor = null;
+        try {
+            cursor = mBatteryUsageSlotDao.getAllAfter(queryTimestamp);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "query() from:" + uri + " error:", e);
+        }
+        Log.w(TAG, "getBatteryUsageSlots() in " + (mClock.millis() - timestamp) + "/ms");
+        return cursor;
+    }
+
+    private List<Integer> getQueryBatteryEventTypes(Uri uri) {
+        Log.d(TAG, "getQueryBatteryEventTypes from uri: " + uri);
+        final String batteryEventTypesParameter =
+                uri.getQueryParameter(DatabaseUtils.QUERY_BATTERY_EVENT_TYPE);
+        if (TextUtils.isEmpty(batteryEventTypesParameter)) {
+            return null;
+        }
+        try {
+            List<Integer> batteryEventTypes = new ArrayList<>();
+            for (String typeString : batteryEventTypesParameter.split(",")) {
+                batteryEventTypes.add(Integer.parseInt(typeString.trim()));
+            }
+            return batteryEventTypes;
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "invalid query value: " + batteryEventTypesParameter, e);
+            return null;
+        }
+    }
+
     // If URI contains query parameter QUERY_KEY_USERID, use the value directly.
     // Otherwise, return null.
     private List<Long> getQueryUserIds(Uri uri) {
         Log.d(TAG, "getQueryUserIds from uri: " + uri);
-        final String value = uri.getQueryParameter(DatabaseUtils.QUERY_KEY_USERID);
-        if (TextUtils.isEmpty(value)) {
-            Log.w(TAG, "empty query value");
+        final String userIdsParameter = uri.getQueryParameter(DatabaseUtils.QUERY_KEY_USERID);
+        if (TextUtils.isEmpty(userIdsParameter)) {
             return null;
         }
         try {
-            return Arrays.asList(value.split(","))
-                    .stream()
-                    .map(s -> Long.parseLong(s.trim()))
-                    .collect(Collectors.toList());
+            List<Long> userIds = new ArrayList<>();
+            for (String idString : userIdsParameter.split(",")) {
+                userIds.add(Long.parseLong(idString.trim()));
+            }
+            return userIds;
         } catch (NumberFormatException e) {
-            Log.e(TAG, "invalid query value: " + value, e);
+            Log.e(TAG, "invalid query value: " + userIdsParameter, e);
             return null;
         }
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index fb1be3e..48a39f4 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -16,14 +16,21 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import android.app.usage.UsageEvents;
 import android.content.Context;
 import android.os.AsyncTask;
 import android.os.BatteryUsageStats;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
+import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
+
 import java.util.List;
+import java.util.Map;
 import java.util.function.Supplier;
 
 /** Load battery usage data in the background. */
@@ -33,6 +40,10 @@
     // For testing only.
     @VisibleForTesting
     static Supplier<List<BatteryEntry>> sFakeBatteryEntryListSupplier;
+    @VisibleForTesting
+    static Supplier<Map<Long, UsageEvents>> sFakeAppUsageEventsSupplier;
+    @VisibleForTesting
+    static Supplier<List<AppUsageEvent>> sFakeUsageEventsListSupplier;
 
     private BatteryUsageDataLoader() {
     }
@@ -45,8 +56,9 @@
     }
 
     @VisibleForTesting
-    static void loadUsageData(final Context context, final boolean isFullChargeStart) {
-        final long start = System.currentTimeMillis();
+    static void loadBatteryStatsData(final Context context, final boolean isFullChargeStart) {
+        BatteryUsageLogUtils.writeLog(context, Action.FETCH_USAGE_DATA, "");
+        final long currentTime = System.currentTimeMillis();
         final BatteryUsageStats batteryUsageStats = DataProcessor.getBatteryUsageStats(context);
         final List<BatteryEntry> batteryEntryList =
                 sFakeBatteryEntryListSupplier != null ? sFakeBatteryEntryListSupplier.get()
@@ -55,25 +67,81 @@
         if (batteryEntryList == null || batteryEntryList.isEmpty()) {
             Log.w(TAG, "getBatteryEntryList() returns null or empty content");
         }
-        final long elapsedTime = System.currentTimeMillis() - start;
+        final long elapsedTime = System.currentTimeMillis() - currentTime;
         Log.d(TAG, String.format("getBatteryUsageStats() in %d/ms", elapsedTime));
         if (isFullChargeStart) {
             DatabaseUtils.recordDateTime(
                     context, DatabaseUtils.KEY_LAST_LOAD_FULL_CHARGE_TIME);
+            DatabaseUtils.sendBatteryEventData(context, ConvertUtils.convertToBatteryEvent(
+                    currentTime, BatteryEventType.FULL_CHARGED, 100));
         }
 
         // Uploads the BatteryEntry data into database.
         DatabaseUtils.sendBatteryEntryData(
-                context, batteryEntryList, batteryUsageStats, isFullChargeStart);
+                context, currentTime, batteryEntryList, batteryUsageStats, isFullChargeStart);
         DataProcessor.closeBatteryUsageStats(batteryUsageStats);
     }
 
+    @VisibleForTesting
+    static void loadAppUsageData(final Context context) {
+        final long start = System.currentTimeMillis();
+        final Map<Long, UsageEvents> appUsageEvents =
+                sFakeAppUsageEventsSupplier != null
+                        ? sFakeAppUsageEventsSupplier.get()
+                        : DataProcessor.getAppUsageEvents(context);
+        if (appUsageEvents == null) {
+            Log.w(TAG, "loadAppUsageData() returns null");
+            return;
+        }
+        final List<AppUsageEvent> appUsageEventList =
+                sFakeUsageEventsListSupplier != null
+                        ? sFakeUsageEventsListSupplier.get()
+                        : DataProcessor.generateAppUsageEventListFromUsageEvents(
+                                context, appUsageEvents);
+        if (appUsageEventList == null || appUsageEventList.isEmpty()) {
+            Log.w(TAG, "loadAppUsageData() returns null or empty content");
+            return;
+        }
+        final long elapsedTime = System.currentTimeMillis() - start;
+        Log.d(TAG, String.format("loadAppUsageData() size=%d in %d/ms", appUsageEventList.size(),
+                elapsedTime));
+        // Uploads the AppUsageEvent data into database.
+        DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
+    }
+
+    private static void preprocessBatteryUsageSlots(final Context context) {
+        final long start = System.currentTimeMillis();
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final BatteryLevelData batteryLevelData = DataProcessManager.getBatteryLevelData(
+                context, handler, /*isFromPeriodJob=*/ true,
+                batteryDiffDataMap -> DatabaseUtils.sendBatteryUsageSlotData(context,
+                        ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap)));
+        if (batteryLevelData == null) {
+            Log.d(TAG, "preprocessBatteryUsageSlots() no new battery usage data.");
+            return;
+        }
+
+        DatabaseUtils.sendBatteryEventData(
+                context, ConvertUtils.convertToBatteryEventList(batteryLevelData));
+        Log.d(TAG, String.format(
+                "preprocessBatteryUsageSlots() batteryLevelData=%s in %d/ms",
+                batteryLevelData, System.currentTimeMillis() - start));
+    }
+
     private static void loadUsageDataSafely(
             final Context context, final boolean isFullChargeStart) {
         try {
-            loadUsageData(context, isFullChargeStart);
+            final long start = System.currentTimeMillis();
+            loadBatteryStatsData(context, isFullChargeStart);
+            if (!isFullChargeStart) {
+                // No app usage data or battery diff data at this time.
+                loadAppUsageData(context);
+                preprocessBatteryUsageSlots(context);
+            }
+            Log.d(TAG, String.format(
+                    "loadUsageDataSafely() in %d/ms", System.currentTimeMillis() - start));
         } catch (RuntimeException e) {
-            Log.e(TAG, "loadUsageData:" + e);
+            Log.e(TAG, "loadUsageData:", e);
         }
     }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index 64b5b77..6d14e1c 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -24,6 +24,8 @@
 import android.util.Log;
 
 import com.android.settings.core.instrumentation.ElapsedTimeUtils;
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
+import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
 import com.android.settings.overlay.FeatureFactory;
 
 import java.time.Duration;
@@ -79,8 +81,13 @@
         if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
             final Intent recheckIntent = new Intent(ACTION_PERIODIC_JOB_RECHECK);
             recheckIntent.setClass(context, BootBroadcastReceiver.class);
-            mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent),
-                    getRescheduleTimeForBootAction(context));
+            final long delayedTime = getRescheduleTimeForBootAction(context);
+            mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), delayedTime);
+
+            // Refreshes the usage source from UsageStatsManager when booting.
+            DatabaseUtils.removeUsageSource(context);
+
+            BatteryUsageLogUtils.writeLog(context, Action.RECHECK_JOB, "delay:" + delayedTime);
         } else if (ACTION_SETUP_WIZARD_FINISHED.equals(action)) {
             ElapsedTimeUtils.storeSuwFinishedTimestamp(context, System.currentTimeMillis());
         }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index 2c98c4b..ec0d01a 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -27,22 +27,27 @@
 import android.os.BatteryUsageStats;
 import android.os.Build;
 import android.os.LocaleList;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.Base64;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
 
 /** A utility class to convert data into another types. */
@@ -67,10 +72,31 @@
     public static final int CONSUMER_TYPE_USER_BATTERY = 2;
     public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3;
 
+    public static final int DEFAULT_USAGE_SOURCE = UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+    public static final int EMPTY_USAGE_SOURCE = -1;
+
+    @VisibleForTesting
+    static int sUsageSource = EMPTY_USAGE_SOURCE;
+
     private ConvertUtils() {
     }
 
-    /** Converts {@link BatteryEntry} to content values */
+    /** Whether {@code consumerType} is app consumer or not. */
+    public static boolean isUidConsumer(final int consumerType) {
+        return consumerType == CONSUMER_TYPE_UID_BATTERY;
+    }
+
+    /** Whether {@code consumerType} is user consumer or not. */
+    public static boolean isUserConsumer(final int consumerType) {
+        return consumerType == CONSUMER_TYPE_USER_BATTERY;
+    }
+
+    /** Whether {@code consumerType} is system consumer or not. */
+    public static boolean isSystemConsumer(final int consumerType) {
+        return consumerType == CONSUMER_TYPE_SYSTEM_BATTERY;
+    }
+
+    /** Converts {@link BatteryEntry} to {@link ContentValues} */
     public static ContentValues convertBatteryEntryToContentValues(
             final BatteryEntry entry,
             final BatteryUsageStats batteryUsageStats,
@@ -113,7 +139,7 @@
         return values;
     }
 
-    /** Converts {@link AppUsageEvent} to content values */
+    /** Converts {@link AppUsageEvent} to {@link ContentValues} */
     public static ContentValues convertAppUsageEventToContentValues(final AppUsageEvent event) {
         final ContentValues values = new ContentValues();
         values.put(AppUsageEventEntity.KEY_UID, event.getUid());
@@ -126,7 +152,7 @@
         return values;
     }
 
-    /** Converts {@link BatteryEvent} to content values */
+    /** Converts {@link BatteryEvent} to {@link ContentValues} */
     public static ContentValues convertBatteryEventToContentValues(final BatteryEvent event) {
         final ContentValues values = new ContentValues();
         values.put(BatteryEventEntity.KEY_TIMESTAMP, event.getTimestamp());
@@ -135,6 +161,16 @@
         return values;
     }
 
+    /** Converts {@link BatteryUsageSlot} to {@link ContentValues} */
+    public static ContentValues convertBatteryUsageSlotToContentValues(
+            final BatteryUsageSlot batteryUsageSlot) {
+        final ContentValues values = new ContentValues(2);
+        values.put(BatteryUsageSlotEntity.KEY_TIMESTAMP, batteryUsageSlot.getStartTimestamp());
+        values.put(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT,
+                Base64.encodeToString(batteryUsageSlot.toByteArray(), Base64.DEFAULT));
+        return values;
+    }
+
     /** Gets the encoded string from {@link BatteryInformation} instance. */
     public static String convertBatteryInformationToString(
             final BatteryInformation batteryInformation) {
@@ -178,10 +214,10 @@
                         /*isFullChargeStart=*/ false));
     }
 
-    /** Converts to {@link AppUsageEvent} from {@link Event} */
+    /** Converts from {@link Event} to {@link AppUsageEvent} */
     @Nullable
     public static AppUsageEvent convertToAppUsageEvent(
-            Context context, final IUsageStatsManager usageStatsManager, final Event event,
+            Context context, IUsageStatsManager usageStatsManager, final Event event,
             final long userId) {
         final String packageName = event.getPackageName();
         if (packageName == null) {
@@ -207,7 +243,8 @@
         }
 
         final String effectivePackageName =
-                getEffectivePackageName(usageStatsManager, packageName, taskRootPackageName);
+                getEffectivePackageName(
+                        context, usageStatsManager, packageName, taskRootPackageName);
         try {
             final long uid = context
                     .getPackageManager()
@@ -228,8 +265,8 @@
         return appUsageEventBuilder.build();
     }
 
-    /** Converts to {@link AppUsageEvent} from {@link Cursor} */
-    public static AppUsageEvent convertToAppUsageEventFromCursor(final Cursor cursor) {
+    /** Converts from {@link Cursor} to {@link AppUsageEvent} */
+    public static AppUsageEvent convertToAppUsageEvent(final Cursor cursor) {
         final AppUsageEvent.Builder eventBuilder = AppUsageEvent.newBuilder();
         eventBuilder.setTimestamp(getLongFromCursor(cursor, AppUsageEventEntity.KEY_TIMESTAMP));
         eventBuilder.setType(
@@ -247,7 +284,7 @@
         return eventBuilder.build();
     }
 
-    /** Converts to {@link BatteryEvent} from {@link BatteryEventType} */
+    /** Converts from {@link BatteryEventType} to {@link BatteryEvent} */
     public static BatteryEvent convertToBatteryEvent(
             long timestamp, BatteryEventType type, int batteryLevel) {
         final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
@@ -257,8 +294,8 @@
         return eventBuilder.build();
     }
 
-    /** Converts to {@link BatteryEvent} from {@link Cursor} */
-    public static BatteryEvent convertToBatteryEventFromCursor(final Cursor cursor) {
+    /** Converts from {@link Cursor} to {@link BatteryEvent} */
+    public static BatteryEvent convertToBatteryEvent(final Cursor cursor) {
         final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
         eventBuilder.setTimestamp(getLongFromCursor(cursor, BatteryEventEntity.KEY_TIMESTAMP));
         eventBuilder.setType(
@@ -270,6 +307,42 @@
         return eventBuilder.build();
     }
 
+    /** Converts from {@link BatteryLevelData} to {@link List<BatteryEvent>} */
+    public static List<BatteryEvent> convertToBatteryEventList(
+            final BatteryLevelData batteryLevelData) {
+        final List<BatteryEvent> batteryEventList = new ArrayList<>();
+        final List<BatteryLevelData.PeriodBatteryLevelData> levelDataList =
+                batteryLevelData.getHourlyBatteryLevelsPerDay();
+        for (BatteryLevelData.PeriodBatteryLevelData oneDayData : levelDataList) {
+            for (int hourIndex = 0; hourIndex < oneDayData.getLevels().size() - 1; hourIndex++) {
+                batteryEventList.add(convertToBatteryEvent(
+                        oneDayData.getTimestamps().get(hourIndex),
+                        BatteryEventType.EVEN_HOUR,
+                        oneDayData.getLevels().get(hourIndex)));
+            }
+        }
+        return batteryEventList;
+    }
+
+    /** Converts from {@link Cursor} to {@link BatteryUsageSlot} */
+    public static BatteryUsageSlot convertToBatteryUsageSlot(final Cursor cursor) {
+        final BatteryUsageSlot defaultInstance = BatteryUsageSlot.getDefaultInstance();
+        final int columnIndex =
+                cursor.getColumnIndex(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT);
+        return columnIndex < 0 ? defaultInstance : BatteryUtils.parseProtoFromString(
+                cursor.getString(columnIndex), defaultInstance);
+    }
+
+    /** Converts from {@link Map<Long, BatteryDiffData>} to {@link List<BatteryUsageSlot>} */
+    public static List<BatteryUsageSlot> convertToBatteryUsageSlotList(
+            final Map<Long, BatteryDiffData> batteryDiffDataMap) {
+        List<BatteryUsageSlot> batteryUsageSlotList = new ArrayList<>();
+        for (BatteryDiffData batteryDiffData : batteryDiffDataMap.values()) {
+            batteryUsageSlotList.add(convertToBatteryUsageSlot(batteryDiffData));
+        }
+        return batteryUsageSlotList;
+    }
+
     /** Converts UTC timestamp to local time string for logging only, so use the US locale for
      *  better readability in debugging. */
     public static String utcToLocalTimeForLogging(long timestamp) {
@@ -323,9 +396,9 @@
      */
     @VisibleForTesting
     static String getEffectivePackageName(
-            final IUsageStatsManager usageStatsManager, final String packageName,
+            Context context, IUsageStatsManager usageStatsManager, final String packageName,
             final String taskRootPackageName) {
-        int usageSource = getUsageSource(usageStatsManager);
+        final int usageSource = getUsageSource(context, usageStatsManager);
         switch (usageSource) {
             case UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY:
                 return !TextUtils.isEmpty(taskRootPackageName)
@@ -370,18 +443,11 @@
         }
     }
 
-    /**
-     * Returns what App Usage Observers will consider the source of usage for an activity.
-     *
-     * @see UsageStatsManager#getUsageSource()
-     */
-    private static int getUsageSource(final IUsageStatsManager usageStatsManager) {
-        try {
-            return usageStatsManager.getUsageSource();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to getUsageSource", e);
-            return UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+    private static int getUsageSource(Context context, IUsageStatsManager usageStatsManager) {
+        if (sUsageSource == EMPTY_USAGE_SOURCE) {
+            sUsageSource = DatabaseUtils.getUsageSource(context, usageStatsManager);
         }
+        return sUsageSource;
     }
 
     private static AppUsageEventType getAppUsageEventType(final int eventType) {
@@ -397,6 +463,100 @@
         }
     }
 
+    private static BatteryUsageDiff convertToBatteryUsageDiff(BatteryDiffEntry batteryDiffEntry) {
+        BatteryUsageDiff.Builder builder = BatteryUsageDiff.newBuilder()
+                .setUid(batteryDiffEntry.mUid)
+                .setUserId(batteryDiffEntry.mUserId)
+                .setIsHidden(batteryDiffEntry.mIsHidden)
+                .setComponentId(batteryDiffEntry.mComponentId)
+                .setConsumerType(batteryDiffEntry.mConsumerType)
+                .setConsumePower(batteryDiffEntry.mConsumePower)
+                .setForegroundUsageConsumePower(batteryDiffEntry.mForegroundUsageConsumePower)
+                .setBackgroundUsageConsumePower(batteryDiffEntry.mBackgroundUsageConsumePower)
+                .setForegroundUsageTime(batteryDiffEntry.mForegroundUsageTimeInMs)
+                .setBackgroundUsageTime(batteryDiffEntry.mBackgroundUsageTimeInMs)
+                .setScreenOnTime(batteryDiffEntry.mScreenOnTimeInMs);
+        if (batteryDiffEntry.mKey != null) {
+            builder.setKey(batteryDiffEntry.mKey);
+        }
+        if (batteryDiffEntry.mLegacyPackageName != null) {
+            builder.setPackageName(batteryDiffEntry.mLegacyPackageName);
+        }
+        if (batteryDiffEntry.mLegacyLabel != null) {
+            builder.setLabel(batteryDiffEntry.mLegacyLabel);
+        }
+        return builder.build();
+    }
+
+    private static BatteryUsageSlot convertToBatteryUsageSlot(
+            final BatteryDiffData batteryDiffData) {
+        if (batteryDiffData == null) {
+            return BatteryUsageSlot.getDefaultInstance();
+        }
+        final BatteryUsageSlot.Builder builder = BatteryUsageSlot.newBuilder()
+                .setStartTimestamp(batteryDiffData.getStartTimestamp())
+                .setEndTimestamp(batteryDiffData.getEndTimestamp())
+                .setStartBatteryLevel(batteryDiffData.getStartBatteryLevel())
+                .setEndBatteryLevel(batteryDiffData.getEndBatteryLevel())
+                .setScreenOnTime(batteryDiffData.getScreenOnTime());
+        for (BatteryDiffEntry batteryDiffEntry : batteryDiffData.getAppDiffEntryList()) {
+            builder.addAppUsage(convertToBatteryUsageDiff(batteryDiffEntry));
+        }
+        for (BatteryDiffEntry batteryDiffEntry : batteryDiffData.getSystemDiffEntryList()) {
+            builder.addSystemUsage(convertToBatteryUsageDiff(batteryDiffEntry));
+        }
+        return builder.build();
+    }
+
+    private static BatteryDiffEntry convertToBatteryDiffEntry(
+            Context context, final BatteryUsageDiff batteryUsageDiff) {
+        return new BatteryDiffEntry(
+                context,
+                batteryUsageDiff.getUid(),
+                batteryUsageDiff.getUserId(),
+                batteryUsageDiff.getKey(),
+                batteryUsageDiff.getIsHidden(),
+                batteryUsageDiff.getComponentId(),
+                batteryUsageDiff.getPackageName(),
+                batteryUsageDiff.getLabel(),
+                batteryUsageDiff.getConsumerType(),
+                batteryUsageDiff.getForegroundUsageTime(),
+                batteryUsageDiff.getBackgroundUsageTime(),
+                batteryUsageDiff.getScreenOnTime(),
+                batteryUsageDiff.getConsumePower(),
+                batteryUsageDiff.getForegroundUsageConsumePower(),
+                /*foregroundServiceUsageConsumePower=*/ 0,
+                batteryUsageDiff.getBackgroundUsageConsumePower(),
+                /*cachedUsageConsumePower=*/ 0);
+    }
+
+    static BatteryDiffData convertToBatteryDiffData(
+            Context context,
+            final BatteryUsageSlot batteryUsageSlot,
+            @NonNull final Set<String> systemAppsPackageNames,
+            @NonNull final Set<Integer> systemAppsUids) {
+        final List<BatteryDiffEntry> appDiffEntries = new ArrayList<>();
+        final List<BatteryDiffEntry> systemDiffEntries = new ArrayList<>();
+        for (BatteryUsageDiff batteryUsageDiff : batteryUsageSlot.getAppUsageList()) {
+            appDiffEntries.add(convertToBatteryDiffEntry(context, batteryUsageDiff));
+        }
+        for (BatteryUsageDiff batteryUsageDiff : batteryUsageSlot.getSystemUsageList()) {
+            systemDiffEntries.add(convertToBatteryDiffEntry(context, batteryUsageDiff));
+        }
+        return new BatteryDiffData(
+                context,
+                batteryUsageSlot.getStartTimestamp(),
+                batteryUsageSlot.getEndTimestamp(),
+                batteryUsageSlot.getStartBatteryLevel(),
+                batteryUsageSlot.getEndBatteryLevel(),
+                batteryUsageSlot.getScreenOnTime(),
+                appDiffEntries,
+                systemDiffEntries,
+                systemAppsPackageNames,
+                systemAppsUids,
+                /*isAccumulated=*/ false);
+    }
+
     private static BatteryInformation constructBatteryInformation(
             final BatteryEntry entry,
             final BatteryUsageStats batteryUsageStats,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
index 0f67e6a..1a226fd 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
@@ -23,6 +23,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -33,10 +34,10 @@
 
 import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Manages the async tasks to process battery and app usage data.
@@ -69,28 +70,37 @@
  */
 public class DataProcessManager {
     private static final String TAG = "DataProcessManager";
+    private static final List<BatteryEventType> POWER_CONNECTION_EVENTS =
+            List.of(BatteryEventType.POWER_CONNECTED, BatteryEventType.POWER_DISCONNECTED);
+    private static final List<BatteryEventType> BATTERY_LEVEL_RECORD_EVENTS =
+            List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR);
 
-    private final Handler mHandler;
-    private final DataProcessor.UsageMapAsyncResponse mCallbackFunction;
-    private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
-    private final List<BatteryEvent> mBatteryEventList = new ArrayList<>();
-
-    private Context mContext;
-    private UserManager mUserManager;
-    private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
-    private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
+    // For testing only.
+    @VisibleForTesting
+    static Map<Long, Map<String, BatteryHistEntry>> sFakeBatteryHistoryMap;
 
     // Raw start timestamp with round to the nearest hour.
-    private long mRawStartTimestamp;
+    private final long mRawStartTimestamp;
+    private final long mLastFullChargeTimestamp;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final UserManager mUserManager;
+    private final OnBatteryDiffDataMapLoadedListener mCallbackFunction;
+    private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
+    private final List<BatteryEvent> mBatteryEventList = new ArrayList<>();
+    private final List<BatteryUsageSlot> mBatteryUsageSlotList = new ArrayList<>();
+    private final List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
+    private final Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
 
     private boolean mIsCurrentBatteryHistoryLoaded = false;
     private boolean mIsCurrentAppUsageLoaded = false;
     private boolean mIsDatabaseAppUsageLoaded = false;
     private boolean mIsBatteryEventLoaded = false;
+    private boolean mIsBatteryUsageSlotLoaded = false;
     // Used to identify whether screen-on time data should be shown in the UI.
     private boolean mShowScreenOnTime = true;
-    // Used to identify whether battery level data should be shown in the UI.
-    private boolean mShowBatteryLevel = true;
+    private Set<String> mSystemAppsPackageNames = null;
+    private Set<Integer> mSystemAppsUids = null;
 
     /**
      * The indexed {@link AppUsagePeriod} list data for each corresponding time slot.
@@ -101,22 +111,33 @@
             mAppUsagePeriodMap;
 
     /**
+     *  A callback listener when all the data is processed.
+     *  This happens when all the async tasks complete and generate the final callback.
+     */
+    public interface OnBatteryDiffDataMapLoadedListener {
+        /** The callback function when all the data is processed. */
+        void onBatteryDiffDataMapLoaded(Map<Long, BatteryDiffData> batteryDiffDataMap);
+    }
+
+    /**
      * Constructor when there exists battery level data.
      */
     DataProcessManager(
             Context context,
             Handler handler,
             final long rawStartTimestamp,
-            @NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction,
+            final long lastFullChargeTimestamp,
+            @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction,
             @NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
             @NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
         mContext = context.getApplicationContext();
         mHandler = handler;
         mUserManager = mContext.getSystemService(UserManager.class);
+        mRawStartTimestamp = rawStartTimestamp;
+        mLastFullChargeTimestamp = lastFullChargeTimestamp;
         mCallbackFunction = callbackFunction;
         mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
         mBatteryHistoryMap = batteryHistoryMap;
-        mRawStartTimestamp = rawStartTimestamp;
     }
 
     /**
@@ -125,31 +146,49 @@
     DataProcessManager(
             Context context,
             Handler handler,
-            @NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction) {
+            @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction) {
         mContext = context.getApplicationContext();
         mHandler = handler;
         mUserManager = mContext.getSystemService(UserManager.class);
         mCallbackFunction = callbackFunction;
+        mRawStartTimestamp = 0L;
+        mLastFullChargeTimestamp = 0L;
+        mHourlyBatteryLevelsPerDay = null;
+        mBatteryHistoryMap = null;
         // When there is no battery level data, don't show screen-on time and battery level chart on
         // the UI.
         mShowScreenOnTime = false;
-        mShowBatteryLevel = false;
     }
 
     /**
      * Starts the async tasks to load battery history data and app usage data.
      */
     public void start() {
+        start(/*isFromPeriodJob=*/ false);
+    }
+
+    /**
+     * Starts the async tasks to load battery history data and app usage data.
+     */
+    public void start(boolean isFromPeriodJob) {
         // If we have battery level data, load the battery history map and app usage simultaneously.
-        if (mShowBatteryLevel) {
-            // Loads the latest battery history data from the service.
-            loadCurrentBatteryHistoryMap();
+        if (mHourlyBatteryLevelsPerDay != null) {
+            if (isFromPeriodJob) {
+                mIsCurrentBatteryHistoryLoaded = true;
+                mIsCurrentAppUsageLoaded = true;
+                mIsBatteryUsageSlotLoaded = true;
+            } else {
+                // Loads the latest battery history data from the service.
+                loadCurrentBatteryHistoryMap();
+                // Loads the latest app usage list from the service.
+                loadCurrentAppUsageList();
+                // Loads existing battery usage slots from database.
+                loadBatteryUsageSlotList();
+            }
             // Loads app usage list from database.
             loadDatabaseAppUsageList();
-            // Loads the latest app usage list from the service.
-            loadCurrentAppUsageList();
             // Loads the battery event list from database.
-            loadBatteryEventList();
+            loadPowerConnectionBatteryEventList();
         } else {
             // If there is no battery level data, only load the battery history data from service
             // and show it as the app list directly.
@@ -193,11 +232,6 @@
         return mShowScreenOnTime;
     }
 
-    @VisibleForTesting
-    boolean getShowBatteryLevel() {
-        return mShowBatteryLevel;
-    }
-
     private void loadCurrentBatteryHistoryMap() {
         new AsyncTask<Void, Void, Map<String, BatteryHistEntry>>() {
             @Override
@@ -323,7 +357,7 @@
         }.execute();
     }
 
-    private void loadBatteryEventList() {
+    private void loadPowerConnectionBatteryEventList() {
         new AsyncTask<Void, Void, List<BatteryEvent>>() {
             @Override
             protected List<BatteryEvent> doInBackground(Void... voids) {
@@ -331,8 +365,10 @@
                 // Loads the battery event data from the database.
                 final List<BatteryEvent> batteryEventList =
                         DatabaseUtils.getBatteryEvents(
-                                mContext, Calendar.getInstance(), mRawStartTimestamp);
-                Log.d(TAG, String.format("execute loadBatteryEventList size=%d in %d/ms",
+                                mContext, Calendar.getInstance(), mRawStartTimestamp,
+                                POWER_CONNECTION_EVENTS);
+                Log.d(TAG, String.format(
+                        "execute loadPowerConnectionBatteryEventList size=%d in %d/ms",
                         batteryEventList.size(), (System.currentTimeMillis() - startTime)));
                 return batteryEventList;
             }
@@ -352,29 +388,55 @@
         }.execute();
     }
 
-    private void loadAndApplyBatteryMapFromServiceOnly() {
-        new AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>>() {
+    private void loadBatteryUsageSlotList() {
+        new AsyncTask<Void, Void, List<BatteryUsageSlot>>() {
             @Override
-            protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
+            protected List<BatteryUsageSlot> doInBackground(Void... voids) {
                 final long startTime = System.currentTimeMillis();
-                final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
-                        DataProcessor.getBatteryUsageMapFromStatsService(mContext);
-                DataProcessor.loadLabelAndIcon(batteryUsageMap);
-                Log.d(TAG, String.format(
-                        "execute loadAndApplyBatteryMapFromServiceOnly size=%d in %d/ms",
-                        batteryUsageMap.size(), (System.currentTimeMillis() - startTime)));
-                return batteryUsageMap;
+                // Loads the battery usage slot data from the database.
+                final List<BatteryUsageSlot> batteryUsageSlotList =
+                        DatabaseUtils.getBatteryUsageSlots(
+                                mContext, Calendar.getInstance(), mLastFullChargeTimestamp);
+                Log.d(TAG, String.format("execute loadBatteryUsageSlotList size=%d in %d/ms",
+                        batteryUsageSlotList.size(), (System.currentTimeMillis() - startTime)));
+                return batteryUsageSlotList;
             }
 
             @Override
-            protected void onPostExecute(
-                    final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
-                // Set the unused variables to null.
-                mContext = null;
+            protected void onPostExecute(final List<BatteryUsageSlot> batteryUsageSlotList) {
+                if (batteryUsageSlotList == null || batteryUsageSlotList.isEmpty()) {
+                    Log.d(TAG, "batteryUsageSlotList is null or empty");
+                } else {
+                    mBatteryUsageSlotList.clear();
+                    mBatteryUsageSlotList.addAll(batteryUsageSlotList);
+                }
+                mIsBatteryUsageSlotLoaded = true;
+                tryToGenerateFinalDataAndApplyCallback();
+            }
+        }.execute();
+    }
+
+    private void loadAndApplyBatteryMapFromServiceOnly() {
+        new AsyncTask<Void, Void, Map<Long, BatteryDiffData>>() {
+            @Override
+            protected Map<Long, BatteryDiffData> doInBackground(Void... voids) {
+                final long startTime = System.currentTimeMillis();
+                final Map<Long, BatteryDiffData> batteryDiffDataMap =
+                        DataProcessor.getBatteryDiffDataMapFromStatsService(
+                                mContext, mRawStartTimestamp, getSystemAppsPackageNames(),
+                                getSystemAppsUids());
+                Log.d(TAG, String.format(
+                        "execute loadAndApplyBatteryMapFromServiceOnly size=%d in %d/ms",
+                        batteryDiffDataMap.size(), (System.currentTimeMillis() - startTime)));
+                return batteryDiffDataMap;
+            }
+
+            @Override
+            protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
                 // Post results back to main thread to refresh UI.
                 if (mHandler != null && mCallbackFunction != null) {
                     mHandler.post(() -> {
-                        mCallbackFunction.onBatteryCallbackDataLoaded(batteryUsageMap);
+                        mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
                     });
                 }
             }
@@ -397,8 +459,8 @@
         }
         // Generates the indexed AppUsagePeriod list data for each corresponding time slot for
         // further use.
-        mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(mRawStartTimestamp,
-                mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList);
+        mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(
+                mContext, mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList);
     }
 
     private void tryToGenerateFinalDataAndApplyCallback() {
@@ -406,38 +468,41 @@
         if (!mIsCurrentBatteryHistoryLoaded
                 || !mIsCurrentAppUsageLoaded
                 || !mIsDatabaseAppUsageLoaded
-                || !mIsBatteryEventLoaded) {
+                || !mIsBatteryEventLoaded
+                || !mIsBatteryUsageSlotLoaded) {
             return;
         }
         generateFinalDataAndApplyCallback();
     }
 
-    private void generateFinalDataAndApplyCallback() {
-        new AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>>() {
+    private synchronized void generateFinalDataAndApplyCallback() {
+        new AsyncTask<Void, Void, Map<Long, BatteryDiffData>>() {
             @Override
-            protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
+            protected Map<Long, BatteryDiffData> doInBackground(Void... voids) {
                 final long startTime = System.currentTimeMillis();
-                final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
-                        DataProcessor.getBatteryUsageMap(
-                                mContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap,
-                                mAppUsagePeriodMap);
-                DataProcessor.loadLabelAndIcon(batteryUsageMap);
-                Log.d(TAG, String.format("execute generateFinalDataAndApplyCallback in %d/ms",
-                        (System.currentTimeMillis() - startTime)));
-                return batteryUsageMap;
+                final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>();
+                for (BatteryUsageSlot batteryUsageSlot : mBatteryUsageSlotList) {
+                    batteryDiffDataMap.put(batteryUsageSlot.getStartTimestamp(),
+                            ConvertUtils.convertToBatteryDiffData(
+                                    mContext, batteryUsageSlot, getSystemAppsPackageNames(),
+                                    getSystemAppsUids()));
+                }
+                batteryDiffDataMap.putAll(DataProcessor.getBatteryDiffDataMap(mContext,
+                        mHourlyBatteryLevelsPerDay, mBatteryHistoryMap, mAppUsagePeriodMap,
+                        getSystemAppsPackageNames(), getSystemAppsUids()));
+
+                Log.d(TAG, String.format(
+                        "execute generateFinalDataAndApplyCallback size=%d in %d/ms",
+                        batteryDiffDataMap.size(), System.currentTimeMillis() - startTime));
+                return batteryDiffDataMap;
             }
 
             @Override
-            protected void onPostExecute(
-                    final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
-                // Set the unused variables to null.
-                mContext = null;
-                mHourlyBatteryLevelsPerDay = null;
-                mBatteryHistoryMap = null;
+            protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
                 // Post results back to main thread to refresh UI.
                 if (mHandler != null && mCallbackFunction != null) {
                     mHandler.post(() -> {
-                        mCallbackFunction.onBatteryCallbackDataLoaded(batteryUsageMap);
+                        mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
                     });
                 }
             }
@@ -445,7 +510,7 @@
     }
 
     // Whether we should load app usage data from service or database.
-    private boolean shouldLoadAppUsageData() {
+    private synchronized boolean shouldLoadAppUsageData() {
         if (!mShowScreenOnTime) {
             return false;
         }
@@ -480,6 +545,20 @@
         return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
     }
 
+    private synchronized Set<String> getSystemAppsPackageNames() {
+        if (mSystemAppsPackageNames == null) {
+            mSystemAppsPackageNames = DataProcessor.getSystemAppsPackageNames(mContext);
+        }
+        return mSystemAppsPackageNames;
+    }
+
+    private synchronized Set<Integer> getSystemAppsUids() {
+        if (mSystemAppsUids == null) {
+            mSystemAppsUids = DataProcessor.getSystemAppsUids(mContext);
+        }
+        return mSystemAppsUids;
+    }
+
     /**
      * @return Returns battery level data and start async task to compute battery diff usage data
      * and load app labels + icons.
@@ -489,14 +568,55 @@
     public static BatteryLevelData getBatteryLevelData(
             Context context,
             @Nullable Handler handler,
-            @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
-            final DataProcessor.UsageMapAsyncResponse asyncResponseDelegate) {
-        if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
-            Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()");
-            new DataProcessManager(context, handler, asyncResponseDelegate).start();
+            final boolean isFromPeriodJob,
+            final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener) {
+        final long start = System.currentTimeMillis();
+        final long lastFullChargeTime = DatabaseUtils.getLastFullChargeTime(context);
+        final List<BatteryEvent> batteryLevelRecordEvents =
+                DatabaseUtils.getBatteryEvents(
+                        context, Calendar.getInstance(), lastFullChargeTime,
+                        BATTERY_LEVEL_RECORD_EVENTS);
+        final long startTimestamp = batteryLevelRecordEvents.isEmpty()
+                ? lastFullChargeTime : batteryLevelRecordEvents.get(0).getTimestamp();
+        final BatteryLevelData batteryLevelData = getPeriodBatteryLevelData(context, handler,
+                startTimestamp, lastFullChargeTime, isFromPeriodJob,
+                onBatteryUsageMapLoadedListener);
+        Log.d(TAG, String.format("execute getBatteryLevelData in %d/ms,"
+                        + " batteryLevelRecordEvents.size=%d",
+                (System.currentTimeMillis() - start), batteryLevelRecordEvents.size()));
+
+        return isFromPeriodJob
+                ? batteryLevelData
+                : BatteryLevelData.combine(batteryLevelData, batteryLevelRecordEvents);
+    }
+
+    private static BatteryLevelData getPeriodBatteryLevelData(
+            Context context,
+            @Nullable Handler handler,
+            final long startTimestamp,
+            final long lastFullChargeTime,
+            final boolean isFromPeriodJob,
+            final OnBatteryDiffDataMapLoadedListener onBatteryDiffDataMapLoadedListener) {
+        final long currentTime = System.currentTimeMillis();
+        Log.d(TAG, String.format("getPeriodBatteryLevelData() startTimestamp=%s",
+                ConvertUtils.utcToLocalTimeForLogging(startTimestamp)));
+        if (isFromPeriodJob
+                && startTimestamp >= TimestampUtils.getLastEvenHourTimestamp(currentTime)) {
+            // Nothing needs to be loaded for period job.
             return null;
         }
+
         handler = handler != null ? handler : new Handler(Looper.getMainLooper());
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                sFakeBatteryHistoryMap != null ? sFakeBatteryHistoryMap
+                        : DatabaseUtils.getHistoryMapSinceLatestRecordBeforeQueryTimestamp(context,
+                                Calendar.getInstance(), startTimestamp, lastFullChargeTime);
+        if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
+            Log.d(TAG, "batteryHistoryMap is null in getPeriodBatteryLevelData()");
+            new DataProcessManager(context, handler, onBatteryDiffDataMapLoadedListener).start();
+            return null;
+        }
+
         // Process raw history map data into hourly timestamps.
         final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap =
                 DataProcessor.getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap);
@@ -505,20 +625,20 @@
                 DataProcessor.getLevelDataThroughProcessedHistoryMap(
                         context, processedBatteryHistoryMap);
         if (batteryLevelData == null) {
-            new DataProcessManager(context, handler, asyncResponseDelegate).start();
+            new DataProcessManager(context, handler, onBatteryDiffDataMapLoadedListener).start();
             Log.d(TAG, "getBatteryLevelData() returns null");
             return null;
         }
 
-        final long rawStartTimestamp = Collections.min(batteryHistoryMap.keySet());
         // Start the async task to compute diff usage data and load labels and icons.
         new DataProcessManager(
                 context,
                 handler,
-                rawStartTimestamp,
-                asyncResponseDelegate,
+                startTimestamp,
+                lastFullChargeTime,
+                onBatteryDiffDataMapLoadedListener,
                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
-                processedBatteryHistoryMap).start();
+                processedBatteryHistoryMap).start(isFromPeriodJob);
 
         return batteryLevelData;
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 6914c30..925cb3a 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -17,6 +17,9 @@
 package com.android.settings.fuelgauge.batteryusage;
 
 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.getEffectivePackageName;
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isSystemConsumer;
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUidConsumer;
+import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
 
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
@@ -44,6 +47,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -54,6 +58,8 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.settingslib.spaprivileged.model.app.AppListRepositoryUtil;
 
+import com.google.common.base.Preconditions;
+
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -76,11 +82,7 @@
     private static final int POWER_COMPONENT_WAKELOCK = 12;
     private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
     private static final int MIN_DAILY_DATA_SIZE = 2;
-    private static final int MIN_TIMESTAMP_DATA_SIZE = 2;
     private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5;
-    // Maximum total time value for each hourly slot cumulative data at most 2 hours.
-    private static final float TOTAL_HOURLY_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2;
-    private static final long MIN_TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2;
     private static final String MEDIASERVER_PACKAGE_NAME = "mediaserver";
     private static final String ANDROID_CORE_APPS_SHARED_USER_ID = "android.uid.shared";
     private static final Map<String, BatteryHistEntry> EMPTY_BATTERY_MAP = new ArrayMap<>();
@@ -159,11 +161,14 @@
         }
         return batteryLevelData == null
                 ? null
-                : getBatteryUsageMap(
-                        context,
-                        batteryLevelData.getHourlyBatteryLevelsPerDay(),
-                        processedBatteryHistoryMap,
-                        /*appUsagePeriodMap=*/ null);
+                : generateBatteryUsageMap(context,
+                        getBatteryDiffDataMap(context,
+                                batteryLevelData.getHourlyBatteryLevelsPerDay(),
+                                processedBatteryHistoryMap,
+                                /*appUsagePeriodMap=*/ null,
+                                getSystemAppsPackageNames(context),
+                                getSystemAppsUids(context)),
+                        batteryLevelData);
     }
 
     /**
@@ -263,7 +268,7 @@
      * </ul>
      *
      * <p>The structure is consistent with the battery usage map returned by
-     * {@code getBatteryUsageMap}.</p>
+     * {@code generateBatteryUsageMap}.</p>
      *
      * <p>{@code Long} stands for the userId.</p>
      * <p>{@code String} stands for the packageName.</p>
@@ -271,7 +276,7 @@
     @Nullable
     public static Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
             generateAppUsagePeriodMap(
-                    final long rawStartTimestamp,
+                    Context context,
                     final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
                     final List<AppUsageEvent> appUsageEventList,
                     final List<BatteryEvent> batteryEventList) {
@@ -305,7 +310,7 @@
                 // The value could be null when there is no data in the hourly slot.
                 dailyMap.put(
                         hourlyIndex,
-                        buildAppUsagePeriodList(hourlyAppUsageEventList, batteryEventList,
+                        buildAppUsagePeriodList(context, hourlyAppUsageEventList, batteryEventList,
                                 startTimestamp, endTimestamp));
             }
         }
@@ -405,8 +410,8 @@
 
     /**
      * @return Returns the processed history map which has interpolated to every hour data.
-     * The start and end timestamp must be the even hours.
-     * The keys of processed history map should contain every hour between the start and end
+     * The start timestamp is the first timestamp in batteryHistoryMap. The end timestamp is current
+     * time. The keys of processed history map should contain every hour between the start and end
      * timestamp. If there's no data in some key, the value will be the empty map.
      */
     static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapWithExpectedTimestamps(
@@ -433,28 +438,23 @@
     static BatteryLevelData getLevelDataThroughProcessedHistoryMap(
             Context context,
             final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap) {
-        final List<Long> timestampList = new ArrayList<>(processedBatteryHistoryMap.keySet());
-        Collections.sort(timestampList);
-        final List<Long> dailyTimestamps = getDailyTimestamps(timestampList);
         // There should be at least the start and end timestamps. Otherwise, return null to not show
         // data in usage chart.
-        if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
+        if (processedBatteryHistoryMap.size() < MIN_DAILY_DATA_SIZE) {
             return null;
         }
-
-        final List<List<Long>> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps);
-        final BatteryLevelData.PeriodBatteryLevelData dailyLevelData =
-                getPeriodBatteryLevelData(context, processedBatteryHistoryMap, dailyTimestamps);
-        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyLevelData =
-                getHourlyPeriodBatteryLevelData(
-                        context, processedBatteryHistoryMap, hourlyTimestamps);
-        return new BatteryLevelData(dailyLevelData, hourlyLevelData);
+        Map<Long, Integer> batteryLevelMap = new ArrayMap<>();
+        for (Long timestamp : processedBatteryHistoryMap.keySet()) {
+            batteryLevelMap.put(
+                    timestamp, getLevel(context, processedBatteryHistoryMap, timestamp));
+        }
+        return new BatteryLevelData(batteryLevelMap);
     }
 
     /**
-     * Computes expected timestamp slots. The start timestamp is the last full charge time.
-     * The end timestamp is current time. The middle timestamps are the sharp hour timestamps
-     * between the start and end timestamps.
+     * Computes expected timestamp slots. The start timestamp is the first timestamp in
+     * rawTimestampList. The end timestamp is current time. The middle timestamps are the sharp hour
+     * timestamps between the start and end timestamps.
      */
     @VisibleForTesting
     static List<Long> getTimestampSlots(final List<Long> rawTimestampList, final long currentTime) {
@@ -477,56 +477,6 @@
         return timestampSlots;
     }
 
-    /**
-     * Computes expected daily timestamp slots.
-     *
-     * The valid result should be composed of 3 parts:
-     * 1) start timestamp
-     * 2) every 00:00 timestamp (default timezone) between the start and end
-     * 3) end timestamp
-     * Otherwise, returns an empty list.
-     */
-    @VisibleForTesting
-    static List<Long> getDailyTimestamps(final List<Long> timestampList) {
-        final List<Long> dailyTimestampList = new ArrayList<>();
-        // If timestamp number is smaller than 2, the following computation is not necessary.
-        if (timestampList.size() < MIN_TIMESTAMP_DATA_SIZE) {
-            return dailyTimestampList;
-        }
-        final long startTime = timestampList.get(0);
-        final long endTime = timestampList.get(timestampList.size() - 1);
-        for (long timestamp = startTime; timestamp < endTime;
-                timestamp = TimestampUtils.getNextDayTimestamp(timestamp)) {
-            dailyTimestampList.add(timestamp);
-        }
-        dailyTimestampList.add(endTime);
-        return dailyTimestampList;
-    }
-
-    @VisibleForTesting
-    static List<List<Long>> getHourlyTimestamps(final List<Long> dailyTimestamps) {
-        final List<List<Long>> hourlyTimestamps = new ArrayList<>();
-        if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
-            return hourlyTimestamps;
-        }
-
-        for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) {
-            final List<Long> hourlyTimestampsPerDay = new ArrayList<>();
-            final long startTime = dailyTimestamps.get(dailyIndex);
-            final long endTime = dailyTimestamps.get(dailyIndex + 1);
-
-            hourlyTimestampsPerDay.add(startTime);
-            for (long timestamp = TimestampUtils.getNextEvenHourTimestamp(startTime);
-                    timestamp < endTime; timestamp += MIN_TIME_SLOT) {
-                hourlyTimestampsPerDay.add(timestamp);
-            }
-            hourlyTimestampsPerDay.add(endTime);
-
-            hourlyTimestamps.add(hourlyTimestampsPerDay);
-        }
-        return hourlyTimestamps;
-    }
-
     @VisibleForTesting
     static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
         if (entryList == null) {
@@ -562,34 +512,102 @@
         return results;
     }
 
+    static Map<Long, BatteryDiffData> getBatteryDiffDataMap(
+            Context context,
+            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
+            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
+            final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
+                    appUsagePeriodMap,
+            final @NonNull Set<String> systemAppsPackageNames,
+            final @NonNull Set<Integer> systemAppsUids) {
+        final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>();
+        final int currentUserId = context.getUserId();
+        final UserHandle userHandle =
+                Utils.getManagedProfile(context.getSystemService(UserManager.class));
+        final int workProfileUserId =
+                userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
+        // Each time slot usage diff data =
+        //     sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
+        // since we want to aggregate every hour usage diff data into a single time slot.
+        for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
+            if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
+                continue;
+            }
+            final List<Long> hourlyTimestamps =
+                    hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
+            for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
+                final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
+                final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
+                final int startBatteryLevel =
+                        hourlyBatteryLevelsPerDay.get(dailyIndex).getLevels().get(hourlyIndex);
+                final int endBatteryLevel =
+                        hourlyBatteryLevelsPerDay.get(dailyIndex).getLevels().get(hourlyIndex + 1);
+                final long slotDuration = endTimestamp - startTimestamp;
+                List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
+                slotBatteryHistoryList.add(
+                        batteryHistoryMap.getOrDefault(startTimestamp, EMPTY_BATTERY_MAP));
+                for (Long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
+                        timestamp < endTimestamp; timestamp += DateUtils.HOUR_IN_MILLIS) {
+                    slotBatteryHistoryList.add(
+                            batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
+                }
+                slotBatteryHistoryList.add(
+                        batteryHistoryMap.getOrDefault(endTimestamp, EMPTY_BATTERY_MAP));
+
+                final BatteryDiffData hourlyBatteryDiffData =
+                        insertHourlyUsageDiffDataPerSlot(
+                                context,
+                                startTimestamp,
+                                endTimestamp,
+                                startBatteryLevel,
+                                endBatteryLevel,
+                                currentUserId,
+                                workProfileUserId,
+                                slotDuration,
+                                systemAppsPackageNames,
+                                systemAppsUids,
+                                appUsagePeriodMap == null
+                                        || appUsagePeriodMap.get(dailyIndex) == null
+                                        ? null
+                                        : appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
+                                slotBatteryHistoryList);
+                batteryDiffDataMap.put(startTimestamp, hourlyBatteryDiffData);
+            }
+        }
+        return batteryDiffDataMap;
+    }
+
     /**
      * @return Returns the indexed battery usage data for each corresponding time slot.
      *
      * <p>There could be 2 cases of the returned value:</p>
      * <ul>
-     * <li>null: empty or invalid data.</li>
-     * <li>non-null: must be a 2d map and composed by 3 parts:</li>
+     * <li> null: empty or invalid data.</li>
+     * <li> 1 part: if batteryLevelData is null.</li>
+     * <p>  [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]</p>
+     * <li> 3 parts: if batteryLevelData is not null.</li>
      * <p>  1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]</p>
      * <p>  2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]</p>
      * <p>  3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]</p>
      * </ul>
      */
-    @Nullable
-    static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMap(
+    static Map<Integer, Map<Integer, BatteryDiffData>> generateBatteryUsageMap(
             final Context context,
-            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
-            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
-            final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
-                    appUsagePeriodMap) {
-        if (batteryHistoryMap.isEmpty()) {
-            return null;
-        }
+            final Map<Long, BatteryDiffData> batteryDiffDataMap,
+            final @Nullable BatteryLevelData batteryLevelData) {
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new ArrayMap<>();
-        final Set<String> systemAppsPackageNames = getSystemAppsPackageNames(context);
-        final Set<Integer> systemAppsUids = getSystemAppsUids(context);
+        if (batteryLevelData == null) {
+            Preconditions.checkArgument(batteryDiffDataMap.size() == 1);
+            BatteryDiffData batteryDiffData = batteryDiffDataMap.values().stream().toList().get(0);
+            final Map<Integer, BatteryDiffData> allUsageMap = new ArrayMap<>();
+            allUsageMap.put(SELECTED_INDEX_ALL, batteryDiffData);
+            resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
+            return resultMap;
+        }
+        List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
+                batteryLevelData.getHourlyBatteryLevelsPerDay();
         // Insert diff data from [0][0] to [maxDailyIndex][maxHourlyIndex].
-        insertHourlyUsageDiffData(context, systemAppsPackageNames, systemAppsUids,
-                hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap, resultMap);
+        insertHourlyUsageDiffData(hourlyBatteryLevelsPerDay, batteryDiffDataMap, resultMap);
         // Insert diff data from [0][SELECTED_INDEX_ALL] to [maxDailyIndex][SELECTED_INDEX_ALL].
         insertDailyUsageDiffData(context, hourlyBatteryLevelsPerDay, resultMap);
         // Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL].
@@ -604,7 +622,10 @@
     @Nullable
     static BatteryDiffData generateBatteryDiffData(
             final Context context,
-            final List<BatteryHistEntry> batteryHistEntryList) {
+            final long startTimestamp,
+            final List<BatteryHistEntry> batteryHistEntryList,
+            final @NonNull Set<String> systemAppsPackageNames,
+            final @NonNull Set<Integer> systemAppsUids) {
         if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) {
             Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()");
             return null;
@@ -626,6 +647,14 @@
             } else {
                 final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
                         context,
+                        entry.mUid,
+                        entry.mUserId,
+                        entry.getKey(),
+                        entry.mIsHidden,
+                        entry.mDrainType,
+                        entry.mPackageName,
+                        entry.mAppLabel,
+                        entry.mConsumerType,
                         entry.mForegroundUsageTimeInMs,
                         entry.mBackgroundUsageTimeInMs,
                         /*screenOnTimeInMs=*/ 0,
@@ -633,8 +662,7 @@
                         entry.mForegroundUsageConsumePower,
                         entry.mForegroundServiceUsageConsumePower,
                         entry.mBackgroundUsageConsumePower,
-                        entry.mCachedUsageConsumePower,
-                        entry);
+                        entry.mCachedUsageConsumePower);
                 if (currentBatteryDiffEntry.isSystemEntry()) {
                     systemEntries.add(currentBatteryDiffEntry);
                 } else {
@@ -647,11 +675,10 @@
         if (appEntries.isEmpty() && systemEntries.isEmpty()) {
             return null;
         }
-
-        final Set<String> systemAppsPackageNames = getSystemAppsPackageNames(context);
-        final Set<Integer> systemAppsUids = getSystemAppsUids(context);
-        return new BatteryDiffData(context, /* screenOnTime= */ 0L, appEntries, systemEntries,
-                systemAppsPackageNames, systemAppsUids, /* isAccumulated= */ false);
+        return new BatteryDiffData(context, startTimestamp, getCurrentTimeMillis(),
+                /* startBatteryLevel =*/ 100, getCurrentLevel(context), /* screenOnTime= */ 0L,
+                appEntries, systemEntries, systemAppsPackageNames, systemAppsUids,
+                /* isAccumulated= */ false);
     }
 
     /**
@@ -661,8 +688,8 @@
     @VisibleForTesting
     @Nullable
     static Map<Long, Map<String, List<AppUsagePeriod>>> buildAppUsagePeriodList(
-            final List<AppUsageEvent> appUsageEvents, final List<BatteryEvent> batteryEventList,
-            final long startTime, final long endTime) {
+            Context context, final List<AppUsageEvent> appUsageEvents,
+            final List<BatteryEvent> batteryEventList, final long startTime, final long endTime) {
         if (appUsageEvents.isEmpty()) {
             return null;
         }
@@ -702,6 +729,7 @@
             final AppUsageEvent firstEvent = usageEvents.get(0);
             final long eventUserId = firstEvent.getUserId();
             final String packageName = getEffectivePackageName(
+                    context,
                     sUsageStatsManager,
                     firstEvent.getPackageName(),
                     firstEvent.getTaskRootPackageName());
@@ -846,21 +874,15 @@
         return getScreenOnTime(appUsageMap.get(userId).get(packageName));
     }
 
-    /**
-     * @return Returns the overall battery usage data from battery stats service directly.
-     *
-     * The returned value should be always a 2d map and composed by only 1 part:
-     * - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
-     */
-    static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService(
-            final Context context) {
-        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new ArrayMap<>();
-        final Map<Integer, BatteryDiffData> allUsageMap = new ArrayMap<>();
-        // Always construct the map whether the value is null or not.
-        allUsageMap.put(SELECTED_INDEX_ALL,
-                generateBatteryDiffData(context, getBatteryHistListFromFromStatsService(context)));
-        resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
-        return resultMap;
+    static Map<Long, BatteryDiffData> getBatteryDiffDataMapFromStatsService(
+            final Context context, final long startTimestamp,
+            @NonNull final Set<String> systemAppsPackageNames,
+            @NonNull final Set<Integer> systemAppsUids) {
+        Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>(1);
+        batteryDiffDataMap.put(startTimestamp, generateBatteryDiffData(
+                context, startTimestamp, getBatteryHistListFromFromStatsService(context),
+                systemAppsPackageNames, systemAppsUids));
+        return batteryDiffDataMap;
     }
 
     static void loadLabelAndIcon(
@@ -879,6 +901,22 @@
         }
     }
 
+    static Set<String> getSystemAppsPackageNames(Context context) {
+        return sTestSystemAppsPackageNames != null ? sTestSystemAppsPackageNames
+                : AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId());
+    }
+
+    static Set<Integer> getSystemAppsUids(Context context) {
+        Set<Integer> result = new ArraySet<>(1);
+        try {
+            result.add(context.getPackageManager().getUidForSharedUser(
+                    ANDROID_CORE_APPS_SHARED_USER_ID));
+        } catch (PackageManager.NameNotFoundException e) {
+            // No Android Core Apps
+        }
+        return result;
+    }
+
     /**
      * Generates the list of {@link AppUsageEvent} within the specific time range.
      * The buffer is added to make sure the app usage calculation near the boundaries is correct.
@@ -1159,28 +1197,6 @@
         resultMap.put(currentSlot, newHistEntryMap);
     }
 
-    private static List<BatteryLevelData.PeriodBatteryLevelData> getHourlyPeriodBatteryLevelData(
-            Context context,
-            final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
-            final List<List<Long>> timestamps) {
-        final List<BatteryLevelData.PeriodBatteryLevelData> levelData = new ArrayList<>();
-        timestamps.forEach(
-                timestampList -> levelData.add(
-                        getPeriodBatteryLevelData(
-                                context, processedBatteryHistoryMap, timestampList)));
-        return levelData;
-    }
-
-    private static BatteryLevelData.PeriodBatteryLevelData getPeriodBatteryLevelData(
-            Context context,
-            final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
-            final List<Long> timestamps) {
-        final List<Integer> levels = new ArrayList<>();
-        timestamps.forEach(
-                timestamp -> levels.add(getLevel(context, processedBatteryHistoryMap, timestamp)));
-        return new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels);
-    }
-
     private static Integer getLevel(
             Context context,
             final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
@@ -1189,13 +1205,12 @@
         if (entryMap == null || entryMap.isEmpty()) {
             Log.e(TAG, "abnormal entry list in the timestamp:"
                     + ConvertUtils.utcToLocalTimeForLogging(timestamp));
-            return null;
+            return BATTERY_LEVEL_UNKNOWN;
         }
         // The current time battery history hasn't been loaded yet, returns the current battery
         // level.
         if (entryMap.containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
-            final Intent intent = BatteryUtils.getBatteryIntent(context);
-            return BatteryStatus.getBatteryLevel(intent);
+            return getCurrentLevel(context);
         }
         // Averages the battery level in each time slot to avoid corner conditions.
         float batteryLevelCounter = 0;
@@ -1205,20 +1220,15 @@
         return Math.round(batteryLevelCounter / entryMap.size());
     }
 
+    private static int getCurrentLevel(Context context) {
+        final Intent intent = BatteryUtils.getBatteryIntent(context);
+        return BatteryStatus.getBatteryLevel(intent);
+    }
+
     private static void insertHourlyUsageDiffData(
-            Context context,
-            final Set<String> systemAppsPackageNames,
-            final Set<Integer> systemAppsUids,
             final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
-            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
-            final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
-                    appUsagePeriodMap,
+            final Map<Long, BatteryDiffData> batteryDiffDataMap,
             final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
-        final int currentUserId = context.getUserId();
-        final UserHandle userHandle =
-                Utils.getManagedProfile(context.getSystemService(UserManager.class));
-        final int workProfileUserId =
-                userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
         // Each time slot usage diff data =
         //     sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
         // since we want to aggregate every hour usage diff data into a single time slot.
@@ -1232,33 +1242,7 @@
                     hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
             for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
                 final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
-                final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
-                final long slotDuration = endTimestamp - startTimestamp;
-                List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
-                slotBatteryHistoryList.add(
-                        batteryHistoryMap.getOrDefault(startTimestamp, EMPTY_BATTERY_MAP));
-                for (Long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
-                        timestamp < endTimestamp; timestamp += DateUtils.HOUR_IN_MILLIS) {
-                    slotBatteryHistoryList.add(
-                            batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
-                }
-                slotBatteryHistoryList.add(
-                        batteryHistoryMap.getOrDefault(endTimestamp, EMPTY_BATTERY_MAP));
-
-                final BatteryDiffData hourlyBatteryDiffData =
-                        insertHourlyUsageDiffDataPerSlot(
-                                context,
-                                currentUserId,
-                                workProfileUserId,
-                                slotDuration,
-                                systemAppsPackageNames,
-                                systemAppsUids,
-                                appUsagePeriodMap == null
-                                        || appUsagePeriodMap.get(dailyIndex) == null
-                                        ? null
-                                        : appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
-                                slotBatteryHistoryList);
-                dailyDiffMap.put(hourlyIndex, hourlyBatteryDiffData);
+                dailyDiffMap.put(hourlyIndex, batteryDiffDataMap.get(startTimestamp));
             }
         }
     }
@@ -1293,6 +1277,10 @@
     @Nullable
     private static BatteryDiffData insertHourlyUsageDiffDataPerSlot(
             final Context context,
+            final long startTimestamp,
+            final long endTimestamp,
+            final int startBatteryLevel,
+            final int endBatteryLevel,
             final int currentUserId,
             final int workProfileUserId,
             final long slotDuration,
@@ -1402,7 +1390,7 @@
                                 currentEntry.mCachedUsageConsumePower,
                                 nextEntry.mCachedUsageConsumePower);
             }
-            if (selectedBatteryEntry.isSystemEntry()
+            if (isSystemConsumer(selectedBatteryEntry.mConsumerType)
                     && selectedBatteryEntry.mDrainType == BatteryConsumer.POWER_COMPONENT_SCREEN) {
                 // Replace Screen system component time with screen on time.
                 foregroundUsageTimeInMs = slotScreenOnTime;
@@ -1448,6 +1436,14 @@
                     backgroundUsageTimeInMs, (long) slotDuration - screenOnTime);
             final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
                     context,
+                    selectedBatteryEntry.mUid,
+                    selectedBatteryEntry.mUserId,
+                    selectedBatteryEntry.getKey(),
+                    selectedBatteryEntry.mIsHidden,
+                    selectedBatteryEntry.mDrainType,
+                    selectedBatteryEntry.mPackageName,
+                    selectedBatteryEntry.mAppLabel,
+                    selectedBatteryEntry.mConsumerType,
                     foregroundUsageTimeInMs,
                     backgroundUsageTimeInMs,
                     screenOnTime,
@@ -1455,8 +1451,7 @@
                     foregroundUsageConsumePower,
                     foregroundServiceUsageConsumePower,
                     backgroundUsageConsumePower,
-                    cachedUsageConsumePower,
-                    selectedBatteryEntry);
+                    cachedUsageConsumePower);
             if (currentBatteryDiffEntry.isSystemEntry()) {
                 systemEntries.add(currentBatteryDiffEntry);
             } else {
@@ -1469,7 +1464,8 @@
             return null;
         }
 
-        return new BatteryDiffData(context, slotScreenOnTime, appEntries, systemEntries,
+        return new BatteryDiffData(context, startTimestamp, endTimestamp, startBatteryLevel,
+                endBatteryLevel, slotScreenOnTime, appEntries, systemEntries,
                 systemAppsPackageNames, systemAppsUids, /* isAccumulated= */ false);
     }
 
@@ -1520,7 +1516,7 @@
             final int currentUserId,
             final int workProfileUserId,
             final BatteryHistEntry batteryHistEntry) {
-        return batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
+        return isUidConsumer(batteryHistEntry.mConsumerType)
                 && batteryHistEntry.mUserId != currentUserId
                 && batteryHistEntry.mUserId != workProfileUserId;
     }
@@ -1532,11 +1528,23 @@
         final List<BatteryDiffEntry> appEntries = new ArrayList<>();
         final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
 
+        long startTimestamp = Long.MAX_VALUE;
+        long endTimestamp = 0;
+        int startBatteryLevel = BATTERY_LEVEL_UNKNOWN;
+        int endBatteryLevel = BATTERY_LEVEL_UNKNOWN;
         long totalScreenOnTime = 0;
         for (BatteryDiffData batteryDiffData : batteryDiffDataList) {
             if (batteryDiffData == null) {
                 continue;
             }
+            if (startTimestamp > batteryDiffData.getStartTimestamp()) {
+                startTimestamp = batteryDiffData.getStartTimestamp();
+                startBatteryLevel = batteryDiffData.getStartBatteryLevel();
+            }
+            if (endTimestamp > batteryDiffData.getEndTimestamp()) {
+                endTimestamp = batteryDiffData.getEndTimestamp();
+                endBatteryLevel = batteryDiffData.getEndBatteryLevel();
+            }
             totalScreenOnTime += batteryDiffData.getScreenOnTime();
             for (BatteryDiffEntry entry : batteryDiffData.getAppDiffEntryList()) {
                 computeUsageDiffDataPerEntry(entry, diffEntryMap);
@@ -1555,8 +1563,9 @@
             }
         }
 
-        return diffEntryList.isEmpty() ? null : new BatteryDiffData(context, totalScreenOnTime,
-                appEntries, systemEntries, /* systemAppsPackageNames= */ new ArraySet<>(),
+        return diffEntryList.isEmpty() ? null : new BatteryDiffData(context, startTimestamp,
+                endTimestamp, startBatteryLevel, endBatteryLevel, totalScreenOnTime, appEntries,
+                systemEntries, /* systemAppsPackageNames= */ new ArraySet<>(),
                 /* systemAppsUids= */ new ArraySet<>(), /* isAccumulated= */ true);
     }
 
@@ -1752,22 +1761,6 @@
         return v2 > v1 ? v2 - v1 : 0;
     }
 
-    private static Set<String> getSystemAppsPackageNames(Context context) {
-        return sTestSystemAppsPackageNames != null ? sTestSystemAppsPackageNames
-                : AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId());
-    }
-
-    private static Set<Integer> getSystemAppsUids(Context context) {
-        Set<Integer> result = new ArraySet<>();
-        try {
-            result.add(context.getPackageManager().getUidForSharedUser(
-                    ANDROID_CORE_APPS_SHARED_USER_ID));
-        } catch (PackageManager.NameNotFoundException e) {
-            // No Android Core Apps
-        }
-        return result;
-    }
-
     private static long getCurrentTimeMillis() {
         return sTestCurrentTimeMillis > 0 ? sTestCurrentTimeMillis : System.currentTimeMillis();
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 0435e45..465afbe 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -15,6 +15,10 @@
  */
 
 package com.android.settings.fuelgauge.batteryusage;
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
+
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageStatsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -28,13 +32,16 @@
 import android.os.BatteryUsageStats;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserManager;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
 import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 
@@ -61,6 +68,7 @@
     static final int DATA_RETENTION_INTERVAL_DAY = 9;
     static final String KEY_LAST_LOAD_FULL_CHARGE_TIME = "last_load_full_charge_time";
     static final String KEY_LAST_UPLOAD_FULL_CHARGE_TIME = "last_upload_full_charge_time";
+    static final String KEY_LAST_USAGE_SOURCE = "last_usage_source";
 
     /** An authority name of the battery content provider. */
     public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
@@ -70,14 +78,20 @@
     public static final String BATTERY_EVENT_TABLE = "BatteryEvent";
     /** A table name for battery usage history. */
     public static final String BATTERY_STATE_TABLE = "BatteryState";
+    /** A table name for battery usage slot. */
+    public static final String BATTERY_USAGE_SLOT_TABLE = "BatteryUsageSlot";
+    /** A path name for last full charge time query. */
+    public static final String LAST_FULL_CHARGE_TIMESTAMP_PATH = "lastFullChargeTimestamp";
+    /** A path name for querying the latest record timestamp in battery state table. */
+    public static final String BATTERY_STATE_LATEST_TIMESTAMP_PATH = "batteryStateLatestTimestamp";
     /** A path name for app usage latest timestamp query. */
     public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp";
-    /** A class name for battery usage data provider. */
-    public static final String SETTINGS_PACKAGE_PATH = "com.android.settings";
     /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
     public static final String QUERY_KEY_TIMESTAMP = "timestamp";
     /** Key for query parameter userid used in APP_USAGE_EVENT_URI **/
     public static final String QUERY_KEY_USERID = "userid";
+    /** Key for query parameter battery event type used in BATTERY_EVENT_URI **/
+    public static final String QUERY_BATTERY_EVENT_TYPE = "batteryEventType";
 
     public static final long INVALID_USER_ID = Integer.MIN_VALUE;
     /**
@@ -107,6 +121,13 @@
                     .authority(AUTHORITY)
                     .appendPath(BATTERY_STATE_TABLE)
                     .build();
+    /** A content URI to access battery usage slots data. */
+    public static final Uri BATTERY_USAGE_SLOT_URI =
+            new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(BATTERY_USAGE_SLOT_TABLE)
+                    .build();
 
     // For testing only.
     @VisibleForTesting
@@ -136,7 +157,7 @@
                         .build();
         final long latestTimestamp =
                 loadAppUsageLatestTimestampFromContentProvider(context, appUsageLatestTimestampUri);
-        final String latestTimestampString = ConvertUtils.utcToLocalTimeForLogging(latestTimestamp);
+        final String latestTimestampString = utcToLocalTimeForLogging(latestTimestamp);
         Log.d(TAG, String.format(
                 "getAppUsageStartTimestampOfUser() userId=%d latestTimestamp=%s in %d/ms",
                 userId, latestTimestampString, (System.currentTimeMillis() - startTime)));
@@ -157,8 +178,7 @@
         // sure the app usage calculation near the boundaries is correct.
         final long queryTimestamp =
                 Math.max(rawStartTimestamp, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS;
-        Log.d(TAG, "sixDayAgoTimestamp: " + ConvertUtils.utcToLocalTimeForLogging(
-                sixDaysAgoTimestamp));
+        Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp));
         final String queryUserIdString = userIds.stream()
                 .map(userId -> String.valueOf(userId))
                 .collect(Collectors.joining(","));
@@ -185,11 +205,15 @@
     public static List<BatteryEvent> getBatteryEvents(
             Context context,
             final Calendar calendar,
-            final long rawStartTimestamp) {
+            final long rawStartTimestamp,
+            final List<BatteryEventType> queryBatteryEventTypes) {
         final long startTime = System.currentTimeMillis();
         final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
         final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
         Log.d(TAG, "getBatteryEvents for timestamp: " + queryTimestamp);
+        final String queryBatteryEventTypesString = queryBatteryEventTypes.stream()
+                .map(type -> String.valueOf(type.getNumber()))
+                .collect(Collectors.joining(","));
         // Builds the content uri everytime to avoid cache.
         final Uri batteryEventUri =
                 new Uri.Builder()
@@ -198,6 +222,8 @@
                         .appendPath(BATTERY_EVENT_TABLE)
                         .appendQueryParameter(
                                 QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .appendQueryParameter(
+                                QUERY_BATTERY_EVENT_TYPE, queryBatteryEventTypesString)
                         .build();
 
         final List<BatteryEvent> batteryEventList =
@@ -207,13 +233,82 @@
         return batteryEventList;
     }
 
-    /** Long: for timestamp and String: for BatteryHistEntry.getKey() */
-    public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
-            Context context, Calendar calendar) {
+    /**
+     * Returns the battery usage slot data after {@code rawStartTimestamp} in battery event table.
+     */
+    public static List<BatteryUsageSlot> getBatteryUsageSlots(
+            Context context,
+            final Calendar calendar,
+            final long rawStartTimestamp) {
         final long startTime = System.currentTimeMillis();
         final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
-        Log.d(TAG, "sixDayAgoTimestamp: " + ConvertUtils.utcToLocalTimeForLogging(
-                sixDaysAgoTimestamp));
+        final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
+        Log.d(TAG, "getBatteryUsageSlots for timestamp: " + queryTimestamp);
+        // Builds the content uri everytime to avoid cache.
+        final Uri batteryUsageSlotUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(AUTHORITY)
+                        .appendPath(BATTERY_USAGE_SLOT_TABLE)
+                        .appendQueryParameter(
+                                QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .build();
+
+        final List<BatteryUsageSlot> batteryUsageSlotList =
+                loadBatteryUsageSlotsFromContentProvider(context, batteryUsageSlotUri);
+        Log.d(TAG, String.format("getBatteryUsageSlots size=%d in %d/ms",
+                batteryUsageSlotList.size(), (System.currentTimeMillis() - startTime)));
+        return batteryUsageSlotList;
+    }
+
+    /** Returns the last full charge time. */
+    public static long getLastFullChargeTime(Context context) {
+        final long startTime = System.currentTimeMillis();
+        // Builds the content uri everytime to avoid cache.
+        final Uri lastFullChargeTimeUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(AUTHORITY)
+                        .appendPath(LAST_FULL_CHARGE_TIMESTAMP_PATH)
+                        .build();
+        final long lastFullChargeTime = loadLastFullChargeTimeFromContentProvider(
+                context, lastFullChargeTimeUri);
+        final String lastFullChargeTimeString = utcToLocalTimeForLogging(lastFullChargeTime);
+        Log.d(TAG, String.format(
+                "getLastFullChargeTime() lastFullChargeTime=%s in %d/ms",
+                lastFullChargeTimeString, (System.currentTimeMillis() - startTime)));
+        return lastFullChargeTime;
+    }
+
+    /** Returns the first battery state timestamp no later than the {@code queryTimestamp}. */
+    @VisibleForTesting
+    static long getBatteryStateLatestTimestampBeforeQueryTimestamp(
+            Context context, final long queryTimestamp) {
+        final long startTime = System.currentTimeMillis();
+        // Builds the content uri everytime to avoid cache.
+        final Uri batteryStateLatestTimestampUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(AUTHORITY)
+                        .appendPath(BATTERY_STATE_LATEST_TIMESTAMP_PATH)
+                        .appendQueryParameter(
+                                QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .build();
+        final long batteryStateLatestTimestamp = loadBatteryStateLatestTimestampFromContentProvider(
+                context, batteryStateLatestTimestampUri);
+        final String batteryStateLatestTimestampString =
+                utcToLocalTimeForLogging(batteryStateLatestTimestamp);
+        Log.d(TAG, String.format(
+                "getBatteryStateLatestTimestamp() batteryStateLatestTimestamp=%s in %d/ms",
+                batteryStateLatestTimestampString, (System.currentTimeMillis() - startTime)));
+        return batteryStateLatestTimestamp;
+    }
+
+    /** Returns the battery history map after the given timestamp. */
+    @VisibleForTesting
+    static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceQueryTimestamp(
+            Context context, final long queryTimestamp) {
+        final long startTime = System.currentTimeMillis();
         // Builds the content uri everytime to avoid cache.
         final Uri batteryStateUri =
                 new Uri.Builder()
@@ -221,20 +316,46 @@
                         .authority(AUTHORITY)
                         .appendPath(BATTERY_STATE_TABLE)
                         .appendQueryParameter(
-                                QUERY_KEY_TIMESTAMP, Long.toString(sixDaysAgoTimestamp))
+                                QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
                         .build();
 
         final Map<Long, Map<String, BatteryHistEntry>> resultMap =
                 loadHistoryMapFromContentProvider(context, batteryStateUri);
         if (resultMap == null || resultMap.isEmpty()) {
-            Log.d(TAG, "getHistoryMapSinceLastFullCharge() returns empty or null");
+            Log.d(TAG, "getBatteryHistoryMap() returns empty or null");
         } else {
-            Log.d(TAG, String.format("getHistoryMapSinceLastFullCharge() size=%d in %d/ms",
+            Log.d(TAG, String.format("getBatteryHistoryMap() size=%d in %d/ms",
                     resultMap.size(), (System.currentTimeMillis() - startTime)));
         }
         return resultMap;
     }
 
+    /**
+     * Returns the battery history map since the latest record no later than the given timestamp.
+     * If there is no record before the given timestamp or the given timestamp is before last full
+     * charge time, returns the history map since last full charge time.
+     */
+    public static Map<Long, Map<String, BatteryHistEntry>>
+            getHistoryMapSinceLatestRecordBeforeQueryTimestamp(Context context, Calendar calendar,
+                    final long queryTimestamp, final long lastFullChargeTime) {
+        final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
+        Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp));
+        final long batteryStateLatestTimestamp =
+                queryTimestamp == 0L ? 0L : getBatteryStateLatestTimestampBeforeQueryTimestamp(
+                        context, queryTimestamp);
+        final long maxTimestamp = Math.max(Math.max(
+                sixDaysAgoTimestamp, lastFullChargeTime), batteryStateLatestTimestamp);
+        return getHistoryMapSinceQueryTimestamp(context, maxTimestamp);
+    }
+
+    /** Returns the history map since last full charge time. */
+    public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
+            Context context, Calendar calendar) {
+        final long lastFullChargeTime = getLastFullChargeTime(context);
+        return getHistoryMapSinceLatestRecordBeforeQueryTimestamp(
+                context, calendar, 0, lastFullChargeTime);
+    }
+
     /** Clears all data in the battery usage database. */
     public static void clearAll(Context context) {
         AsyncTask.execute(() -> {
@@ -244,6 +365,7 @@
                 database.appUsageEventDao().clearAll();
                 database.batteryEventDao().clearAll();
                 database.batteryStateDao().clearAll();
+                database.batteryUsageSlotDao().clearAll();
             } catch (RuntimeException e) {
                 Log.e(TAG, "clearAll() failed", e);
             }
@@ -261,6 +383,7 @@
                 database.appUsageEventDao().clearAllBefore(earliestTimestamp);
                 database.batteryEventDao().clearAllBefore(earliestTimestamp);
                 database.batteryStateDao().clearAllBefore(earliestTimestamp);
+                database.batteryUsageSlotDao().clearAllBefore(earliestTimestamp);
             } catch (RuntimeException e) {
                 Log.e(TAG, "clearAllBefore() failed", e);
             }
@@ -289,7 +412,7 @@
                         /*user=*/ context.getSystemService(UserManager.class)
                                 .getProfileParent(context.getUser()));
             } catch (PackageManager.NameNotFoundException e) {
-                Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
+                Log.e(TAG, "context.createPackageContextAsUser() fail:", e);
                 return null;
             }
         }
@@ -316,7 +439,7 @@
                 resolver.notifyChange(APP_USAGE_EVENT_URI, /*observer=*/ null);
                 Log.d(TAG, "insert() app usage events data into database");
             } catch (Exception e) {
-                Log.e(TAG, "bulkInsert() app usage data into database error:\n" + e);
+                Log.e(TAG, "bulkInsert() app usage data into database error:", e);
             }
         }
         Log.d(TAG, String.format("sendAppUsageEventData() size=%d in %d/ms",
@@ -342,8 +465,65 @@
         return contentValues;
     }
 
+    static List<ContentValues> sendBatteryEventData(
+            final Context context, final List<BatteryEvent> batteryEventList) {
+        final long startTime = System.currentTimeMillis();
+        // Creates the ContentValues list to insert them into provider.
+        final List<ContentValues> valuesList = new ArrayList<>();
+        batteryEventList.stream()
+                .forEach(batteryEvent -> valuesList.add(
+                        ConvertUtils.convertBatteryEventToContentValues(batteryEvent)));
+        int size = 0;
+        final ContentResolver resolver = context.getContentResolver();
+        // Inserts all ContentValues into battery provider.
+        if (!valuesList.isEmpty()) {
+            final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
+            valuesList.toArray(valuesArray);
+            try {
+                size = resolver.bulkInsert(BATTERY_EVENT_URI, valuesArray);
+                resolver.notifyChange(BATTERY_EVENT_URI, /*observer=*/ null);
+                Log.d(TAG, "insert() battery event data into database");
+            } catch (Exception e) {
+                Log.e(TAG, "bulkInsert() battery event data into database error:", e);
+            }
+        }
+        Log.d(TAG, String.format("sendBatteryEventData() size=%d in %d/ms",
+                size, (System.currentTimeMillis() - startTime)));
+        clearMemory();
+        return valuesList;
+    }
+
+    static List<ContentValues> sendBatteryUsageSlotData(
+            final Context context, final List<BatteryUsageSlot> batteryUsageSlotList) {
+        final long startTime = System.currentTimeMillis();
+        // Creates the ContentValues list to insert them into provider.
+        final List<ContentValues> valuesList = new ArrayList<>();
+        batteryUsageSlotList.stream()
+                .forEach(batteryUsageSlot -> valuesList.add(
+                        ConvertUtils.convertBatteryUsageSlotToContentValues(batteryUsageSlot)));
+        int size = 0;
+        final ContentResolver resolver = context.getContentResolver();
+        // Inserts all ContentValues into battery provider.
+        if (!valuesList.isEmpty()) {
+            final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
+            valuesList.toArray(valuesArray);
+            try {
+                size = resolver.bulkInsert(BATTERY_USAGE_SLOT_URI, valuesArray);
+                resolver.notifyChange(BATTERY_USAGE_SLOT_URI, /*observer=*/ null);
+                Log.d(TAG, "insert() battery usage slots data into database");
+            } catch (Exception e) {
+                Log.e(TAG, "bulkInsert() battery usage slots data into database error:", e);
+            }
+        }
+        Log.d(TAG, String.format("sendBatteryUsageSlotData() size=%d in %d/ms",
+                size, (System.currentTimeMillis() - startTime)));
+        clearMemory();
+        return valuesList;
+    }
+
     static List<ContentValues> sendBatteryEntryData(
             final Context context,
+            final long snapshotTimestamp,
             final List<BatteryEntry> batteryEntryList,
             final BatteryUsageStats batteryUsageStats,
             final boolean isFullChargeStart) {
@@ -360,7 +540,6 @@
         final int batteryHealth = intent.getIntExtra(
                 BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
         // We should use the same timestamp for each data snapshot.
-        final long snapshotTimestamp = Clock.systemUTC().millis();
         final long snapshotBootTimestamp = SystemClock.elapsedRealtime();
 
         // Creates the ContentValues list to insert them into provider.
@@ -395,6 +574,7 @@
 
         int size = 1;
         final ContentResolver resolver = context.getContentResolver();
+        String errorMessage = "";
         // Inserts all ContentValues into battery provider.
         if (!valuesList.isEmpty()) {
             final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
@@ -404,7 +584,7 @@
                 Log.d(TAG, "insert() battery states data into database with isFullChargeStart:"
                         + isFullChargeStart);
             } catch (Exception e) {
-                Log.e(TAG, "bulkInsert() battery states data into database error:\n" + e);
+                Log.e(TAG, "bulkInsert() data into database error:", e);
             }
         } else {
             // Inserts one fake data into battery provider.
@@ -424,11 +604,15 @@
                         + isFullChargeStart);
 
             } catch (Exception e) {
-                Log.e(TAG, "insert() data into database error:\n" + e);
+                Log.e(TAG, "insert() data into database error:", e);
             }
             valuesList.add(contentValues);
         }
         resolver.notifyChange(BATTERY_CONTENT_URI, /*observer=*/ null);
+        BatteryUsageLogUtils.writeLog(
+                context,
+                Action.INSERT_USAGE_DATA,
+                "size=" + size + " " + errorMessage);
         Log.d(TAG, String.format("sendBatteryEntryData() size=%d in %d/ms",
                 size, (System.currentTimeMillis() - startTime)));
         if (isFullChargeStart) {
@@ -459,11 +643,41 @@
                 SHARED_PREFS_FILE, Context.MODE_PRIVATE);
     }
 
+    static void removeUsageSource(Context context) {
+        final SharedPreferences sharedPreferences = getSharedPreferences(context);
+        if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) {
+            sharedPreferences.edit().remove(KEY_LAST_USAGE_SOURCE).apply();
+        }
+    }
+
+    /**
+     * Returns what App Usage Observers will consider the source of usage for an activity.
+     *
+     * @see UsageStatsManager#getUsageSource()
+     */
+    static int getUsageSource(Context context, IUsageStatsManager usageStatsManager) {
+        final SharedPreferences sharedPreferences = getSharedPreferences(context);
+        if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) {
+            return sharedPreferences
+                    .getInt(KEY_LAST_USAGE_SOURCE, ConvertUtils.DEFAULT_USAGE_SOURCE);
+        }
+        int usageSource = ConvertUtils.DEFAULT_USAGE_SOURCE;
+
+        try {
+            usageSource = usageStatsManager.getUsageSource();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to getUsageSource", e);
+        }
+        if (sharedPreferences != null) {
+            sharedPreferences.edit().putInt(KEY_LAST_USAGE_SOURCE, usageSource).apply();
+        }
+        return usageSource;
+    }
+
     static void recordDateTime(Context context, String preferenceKey) {
         final SharedPreferences sharedPreferences = getSharedPreferences(context);
         if (sharedPreferences != null) {
-            final String currentTime = ConvertUtils.utcToLocalTimeForLogging(
-                    System.currentTimeMillis());
+            final String currentTime = utcToLocalTimeForLogging(System.currentTimeMillis());
             sharedPreferences.edit().putString(preferenceKey, currentTime).apply();
         }
     }
@@ -491,11 +705,6 @@
             cursor.moveToFirst();
             // There is only one column returned so use the index 0 directly.
             final long latestTimestamp = cursor.getLong(/*columnIndex=*/ 0);
-            try {
-                cursor.close();
-            } catch (Exception e) {
-                Log.e(TAG, "cursor.close() failed", e);
-            }
             // If there is no data for this user, 0 will be returned from the database.
             return latestTimestamp == 0 ? INVALID_USER_ID : latestTimestamp;
         }
@@ -514,14 +723,9 @@
             if (cursor == null || cursor.getCount() == 0) {
                 return appUsageEventList;
             }
-            // Loads and recovers all AppUsageEvent data from cursor.
+            // Loads and converts all AppUsageEvent data from cursor.
             while (cursor.moveToNext()) {
-                appUsageEventList.add(ConvertUtils.convertToAppUsageEventFromCursor(cursor));
-            }
-            try {
-                cursor.close();
-            } catch (Exception e) {
-                Log.e(TAG, "cursor.close() failed", e);
+                appUsageEventList.add(ConvertUtils.convertToAppUsageEvent(cursor));
             }
         }
         return appUsageEventList;
@@ -540,22 +744,74 @@
             if (cursor == null || cursor.getCount() == 0) {
                 return batteryEventList;
             }
-            // Loads and recovers all AppUsageEvent data from cursor.
+            // Loads and converts all AppUsageEvent data from cursor.
             while (cursor.moveToNext()) {
-                batteryEventList.add(ConvertUtils.convertToBatteryEventFromCursor(cursor));
-            }
-            try {
-                cursor.close();
-            } catch (Exception e) {
-                Log.e(TAG, "cursor.close() failed", e);
+                batteryEventList.add(ConvertUtils.convertToBatteryEvent(cursor));
             }
         }
         return batteryEventList;
     }
 
+    private static List<BatteryUsageSlot> loadBatteryUsageSlotsFromContentProvider(
+            Context context, Uri batteryUsageSlotUri) {
+        final List<BatteryUsageSlot> batteryUsageSlotList = new ArrayList<>();
+        context = getParentContext(context);
+        if (context == null) {
+            return batteryUsageSlotList;
+        }
+        try (Cursor cursor = sFakeSupplier != null
+                ? sFakeSupplier.get()
+                : context.getContentResolver().query(batteryUsageSlotUri, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return batteryUsageSlotList;
+            }
+            // Loads and converts all AppUsageEvent data from cursor.
+            while (cursor.moveToNext()) {
+                batteryUsageSlotList.add(ConvertUtils.convertToBatteryUsageSlot(cursor));
+            }
+        }
+        return batteryUsageSlotList;
+    }
+
+    private static long loadLastFullChargeTimeFromContentProvider(
+            Context context, final Uri lastFullChargeTimeUri) {
+        // We have already make sure the context here is with profile parent's user identity. Don't
+        // need to check whether current user is work profile.
+        try (Cursor cursor = sFakeSupplier != null
+                ? sFakeSupplier.get()
+                : context.getContentResolver().query(
+                        lastFullChargeTimeUri, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return 0L;
+            }
+            cursor.moveToFirst();
+            // There is only one column returned so use the index 0 directly.
+            final long lastFullChargeTime = cursor.getLong(/*columnIndex=*/ 0);
+            return lastFullChargeTime;
+        }
+    }
+
+    private static long loadBatteryStateLatestTimestampFromContentProvider(
+            Context context, final Uri batteryStateLatestTimestampUri) {
+        // We have already make sure the context here is with profile parent's user identity. Don't
+        // need to check whether current user is work profile.
+        try (Cursor cursor = sFakeSupplier != null
+                ? sFakeSupplier.get()
+                : context.getContentResolver().query(
+                        batteryStateLatestTimestampUri, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return 0L;
+            }
+            cursor.moveToFirst();
+            // There is only one column returned so use the index 0 directly.
+            final long batteryStateLatestTimestamp = cursor.getLong(/*columnIndex=*/ 0);
+            return batteryStateLatestTimestamp;
+        }
+    }
+
     private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
             Context context, Uri batteryStateUri) {
-        context = DatabaseUtils.getParentContext(context);
+        context = getParentContext(context);
         if (context == null) {
             return null;
         }
@@ -565,7 +821,7 @@
             if (cursor == null || cursor.getCount() == 0) {
                 return resultMap;
             }
-            // Loads and recovers all BatteryHistEntry data from cursor.
+            // Loads and converts all BatteryHistEntry data from cursor.
             while (cursor.moveToNext()) {
                 final BatteryHistEntry entry = new BatteryHistEntry(cursor);
                 final long timestamp = entry.mTimestamp;
@@ -578,11 +834,6 @@
                 }
                 batteryHistEntryMap.put(key, entry);
             }
-            try {
-                cursor.close();
-            } catch (Exception e) {
-                Log.e(TAG, "cursor.close() failed", e);
-            }
         }
         return resultMap;
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java
index 3d78c00..8c0e66c 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java
@@ -24,6 +24,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
+import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
 import com.android.settings.overlay.FeatureFactory;
 
 import java.time.Clock;
@@ -76,8 +78,11 @@
         final long triggerAtMillis = getTriggerAtMillis(mContext, Clock.systemUTC(), fromBoot);
         mAlarmManager.setExactAndAllowWhileIdle(
                 AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
-        Log.d(TAG, "schedule next alarm job at "
-                + ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis));
+
+        final String utcToLocalTime = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis);
+        BatteryUsageLogUtils.writeLog(
+                mContext, Action.SCHEDULE_JOB, "triggerTime=" + utcToLocalTime);
+        Log.d(TAG, "schedule next alarm job at " + utcToLocalTime);
     }
 
     void cancelJob(PendingIntent pendingIntent) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
index 3ca4532..2371a19 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
@@ -22,6 +22,9 @@
 import android.content.Intent;
 import android.util.Log;
 
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
+import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
+
 /** Receives the periodic alarm {@link PendingIntent} callback. */
 public final class PeriodicJobReceiver extends BroadcastReceiver {
     private static final String TAG = "PeriodicJobReceiver";
@@ -39,8 +42,8 @@
             Log.w(TAG, "do not refresh job for work profile action=" + action);
             return;
         }
+        BatteryUsageLogUtils.writeLog(context, Action.EXECUTE_JOB, "");
         BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ false);
-        AppUsageDataLoader.enqueueWork(context);
         Log.d(TAG, "refresh periodic job from action=" + action);
         PeriodicJobManager.getInstance(context).refreshJob(/*fromBoot=*/ false);
         DatabaseUtils.clearExpiredDataIfNeeded(context);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 7c4478e..e4f8b39 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -21,12 +21,13 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.provider.SearchIndexableResource;
 import android.util.Log;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.loader.app.LoaderManager;
 import androidx.loader.content.Loader;
@@ -34,14 +35,18 @@
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.fuelgauge.BatteryBroadcastReceiver;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.utils.AsyncLoaderCompat;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /** Advanced power usage. */
 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
@@ -53,16 +58,17 @@
     @VisibleForTesting
     BatteryHistoryPreference mHistPref;
     @VisibleForTesting
-    Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
-    @VisibleForTesting
-    final BatteryHistoryLoaderCallbacks mBatteryHistoryLoaderCallbacks =
-            new BatteryHistoryLoaderCallbacks();
+    final BatteryLevelDataLoaderCallbacks mBatteryLevelDataLoaderCallbacks =
+            new BatteryLevelDataLoaderCallbacks();
 
     private boolean mIsChartDataLoaded = false;
+    private long mResumeTimestamp;
     private BatteryChartPreferenceController mBatteryChartPreferenceController;
+    private Optional<BatteryLevelData> mBatteryLevelData;
 
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final ContentObserver mBatteryObserver =
-            new ContentObserver(new Handler()) {
+            new ContentObserver(mHandler) {
                 @Override
                 public void onChange(boolean selfChange) {
                     Log.d(TAG, "onBatteryContentChange: " + selfChange);
@@ -77,6 +83,7 @@
         super.onCreate(icicle);
         mHistPref = findPreference(KEY_BATTERY_CHART);
         setBatteryChartPreferenceController();
+        AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext()));
     }
 
     @Override
@@ -107,6 +114,7 @@
         super.onPause();
         // Resets the flag to reload usage data in onResume() callback.
         mIsChartDataLoaded = false;
+        mBatteryLevelData = null;
         final Uri uri = DatabaseUtils.BATTERY_CONTENT_URI;
         if (uri != null) {
             getContext().getContentResolver().unregisterContentObserver(mBatteryObserver);
@@ -116,6 +124,7 @@
     @Override
     public void onResume() {
         super.onResume();
+        mResumeTimestamp = System.currentTimeMillis();
         final Uri uri = DatabaseUtils.BATTERY_CONTENT_URI;
         if (uri != null) {
             getContext().getContentResolver().registerContentObserver(
@@ -143,24 +152,22 @@
         controllers.add(screenOnTimeController);
         controllers.add(batteryUsageBreakdownController);
         setBatteryChartPreferenceController();
+
+        final PowerUsageFeatureProvider powerUsageFeatureProvider =
+                FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context);
+        if (powerUsageFeatureProvider.isBatteryTipsEnabled()) {
+            BatteryTipsController batteryTipsController = new BatteryTipsController(context);
+            mBatteryChartPreferenceController.setOnBatteryTipsUpdatedListener(
+                    batteryTipsController::handleBatteryTipsCardUpdated);
+            controllers.add(batteryTipsController);
+        }
+
         return controllers;
     }
 
     @Override
-    protected boolean isBatteryHistoryNeeded() {
-        return true;
-    }
-
-    @Override
     protected void refreshUi(@BatteryUpdateType int refreshType) {
-        final Context context = getContext();
-        if (context == null) {
-            return;
-        }
-        updatePreference(mHistPref);
-        if (mBatteryChartPreferenceController != null && mBatteryHistoryMap != null) {
-            mBatteryChartPreferenceController.setBatteryHistoryMap(mBatteryHistoryMap);
-        }
+        // Do nothing
     }
 
     @Override
@@ -169,11 +176,32 @@
         bundle.putInt(KEY_REFRESH_TYPE, refreshType);
         if (!mIsChartDataLoaded) {
             mIsChartDataLoaded = true;
-            restartLoader(LoaderIndex.BATTERY_HISTORY_LOADER, bundle,
-                    mBatteryHistoryLoaderCallbacks);
+            restartLoader(LoaderIndex.BATTERY_LEVEL_DATA_LOADER, bundle,
+                    mBatteryLevelDataLoaderCallbacks);
         }
     }
 
+    private void onBatteryLevelDataUpdate(BatteryLevelData batteryLevelData) {
+        mBatteryLevelData = Optional.ofNullable(batteryLevelData);
+        if (mBatteryChartPreferenceController != null) {
+            mBatteryChartPreferenceController.onBatteryLevelDataUpdate(batteryLevelData);
+            Log.d(TAG, String.format("Battery chart shows in %d millis",
+                    System.currentTimeMillis() - mResumeTimestamp));
+        }
+    }
+
+    private void onBatteryDiffDataMapUpdate(Map<Long, BatteryDiffData> batteryDiffDataMap) {
+        if (mBatteryLevelData != null && mBatteryChartPreferenceController != null) {
+            Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
+                    DataProcessor.generateBatteryUsageMap(
+                            getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null));
+            DataProcessor.loadLabelAndIcon(batteryUsageMap);
+            mBatteryChartPreferenceController.onBatteryUsageMapUpdate(batteryUsageMap);
+        }
+        Log.d(TAG, String.format("Battery usage list shows in %d millis",
+                System.currentTimeMillis() - mResumeTimestamp));
+    }
+
     private void setBatteryChartPreferenceController() {
         if (mHistPref != null && mBatteryChartPreferenceController != null) {
             mHistPref.setChartPreferenceController(mBatteryChartPreferenceController);
@@ -204,28 +232,31 @@
                 }
             };
 
-    private class BatteryHistoryLoaderCallbacks
-            implements LoaderManager.LoaderCallbacks<Map<Long, Map<String, BatteryHistEntry>>> {
-        private int mRefreshType;
-
+    private class BatteryLevelDataLoaderCallbacks
+            implements LoaderManager.LoaderCallbacks<BatteryLevelData> {
         @Override
-        @NonNull
-        public Loader<Map<Long, Map<String, BatteryHistEntry>>> onCreateLoader(
-                int id, Bundle bundle) {
-            mRefreshType = bundle.getInt(KEY_REFRESH_TYPE);
-            return new BatteryHistoryLoader(getContext());
+        public Loader<BatteryLevelData> onCreateLoader(int id, Bundle bundle) {
+            return new AsyncLoaderCompat<BatteryLevelData>(getContext().getApplicationContext()) {
+                @Override
+                protected void onDiscardResult(BatteryLevelData result) {}
+
+                @Override
+                public BatteryLevelData loadInBackground() {
+                    return DataProcessManager.getBatteryLevelData(
+                            getContext(), mHandler, /*isFromPeriodJob=*/ false,
+                            map -> PowerUsageAdvanced.this.onBatteryDiffDataMapUpdate(map));
+                }
+            };
         }
 
         @Override
-        public void onLoadFinished(Loader<Map<Long, Map<String, BatteryHistEntry>>> loader,
-                Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
-            mBatteryHistoryMap = batteryHistoryMap;
-            PowerUsageAdvanced.this.onLoadFinished(mRefreshType);
+        public void onLoadFinished(Loader<BatteryLevelData> loader,
+                BatteryLevelData batteryLevelData) {
+            PowerUsageAdvanced.this.onBatteryLevelDataUpdate(batteryLevelData);
         }
 
         @Override
-        public void onLoaderReset(Loader<Map<Long, Map<String, BatteryHistEntry>>> loader) {
+        public void onLoaderReset(Loader<BatteryLevelData> loader) {
         }
     }
-
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java
index ed3a921..22856b6 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBase.java
@@ -32,7 +32,6 @@
 
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.fuelgauge.BatteryBroadcastReceiver;
-import com.android.settings.fuelgauge.BatteryUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -63,14 +62,14 @@
             LoaderIndex.BATTERY_USAGE_STATS_LOADER,
             LoaderIndex.BATTERY_INFO_LOADER,
             LoaderIndex.BATTERY_TIP_LOADER,
-            LoaderIndex.BATTERY_HISTORY_LOADER
+            LoaderIndex.BATTERY_LEVEL_DATA_LOADER
 
     })
     public @interface LoaderIndex {
         int BATTERY_USAGE_STATS_LOADER = 0;
         int BATTERY_INFO_LOADER = 1;
         int BATTERY_TIP_LOADER = 2;
-        int BATTERY_HISTORY_LOADER = 3;
+        int BATTERY_LEVEL_DATA_LOADER = 3;
     }
 
     @Override
@@ -108,7 +107,7 @@
     protected void restartBatteryStatsLoader(int refreshType) {
         final Bundle bundle = new Bundle();
         bundle.putInt(KEY_REFRESH_TYPE, refreshType);
-        bundle.putBoolean(KEY_INCLUDE_HISTORY, isBatteryHistoryNeeded());
+        bundle.putBoolean(KEY_INCLUDE_HISTORY, false);
         restartLoader(LoaderIndex.BATTERY_USAGE_STATS_LOADER, bundle,
                 mBatteryUsageStatsLoaderCallbacks);
     }
@@ -137,14 +136,6 @@
 
     protected abstract void refreshUi(@BatteryUpdateType int refreshType);
 
-    protected abstract boolean isBatteryHistoryNeeded();
-
-    protected void updatePreference(BatteryHistoryPreference historyPref) {
-        final long startTime = System.currentTimeMillis();
-        historyPref.setBatteryUsageStats(mBatteryUsageStats);
-        BatteryUtils.logRuntime(TAG, "updatePreference", startTime);
-    }
-
     private class BatteryUsageStatsLoaderCallbacks
             implements LoaderManager.LoaderCallbacks<BatteryUsageStats> {
         private int mRefreshType;
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java
index 0bec490..40fd3f4 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java
@@ -45,7 +45,6 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
-import com.android.settingslib.widget.LayoutPreference;
 
 import java.util.List;
 
@@ -69,8 +68,6 @@
     @VisibleForTesting
     BatteryUtils mBatteryUtils;
     @VisibleForTesting
-    LayoutPreference mBatteryLayoutPref;
-    @VisibleForTesting
     BatteryInfo mBatteryInfo;
 
     @VisibleForTesting
@@ -208,11 +205,6 @@
         return R.string.help_url_battery;
     }
 
-    @Override
-    protected boolean isBatteryHistoryNeeded() {
-        return false;
-    }
-
     protected void refreshUi(@BatteryUpdateType int refreshType) {
         final Context context = getContext();
         if (context == null) {
@@ -240,11 +232,6 @@
     }
 
     @VisibleForTesting
-    void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
-        mBatteryLayoutPref = layoutPreference;
-    }
-
-    @VisibleForTesting
     void initFeatureProvider() {
         final Context context = getContext();
         mPowerFeatureProvider = FeatureFactory.getFactory(context)
diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java
new file mode 100644
index 0000000..cb2f394
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.bugreport;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Base64;
+
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLog;
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry;
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/** Writes and reads a historical log of battery usage periodic job events. */
+public final class BatteryUsageLogUtils {
+    private static final String TAG = "BatteryUsageLogUtils";
+    private static final String BATTERY_USAGE_FILE_NAME = "battery_usage_historical_logs";
+    private static final String LOGS_KEY = "battery_usage_logs_key";
+
+    // 24 hours x 4 events every hour x 3 days
+    static final int MAX_ENTRIES = 288;
+
+    private BatteryUsageLogUtils() {}
+
+    /** Write the log into the {@link SharedPreferences}. */
+    public static void writeLog(Context context, Action action, String actionDescription) {
+        final SharedPreferences sharedPreferences = getSharedPreferences(context);
+        final BatteryUsageHistoricalLogEntry newLogEntry =
+                BatteryUsageHistoricalLogEntry.newBuilder()
+                        .setTimestamp(System.currentTimeMillis())
+                        .setAction(action)
+                        .setActionDescription(actionDescription)
+                        .build();
+
+        final BatteryUsageHistoricalLog existingLog =
+                parseLogFromString(sharedPreferences.getString(LOGS_KEY, ""));
+        final BatteryUsageHistoricalLog.Builder newLogBuilder = existingLog.toBuilder();
+        // Prune old entries to limit the max logging data count.
+        if (existingLog.getLogEntryCount() >= MAX_ENTRIES) {
+            newLogBuilder.removeLogEntry(0);
+        }
+        newLogBuilder.addLogEntry(newLogEntry);
+
+        final String loggingContent =
+                Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT);
+        sharedPreferences
+                .edit()
+                .putString(LOGS_KEY, loggingContent)
+                .apply();
+    }
+
+    /** Prints the historical log that has previously been stored by this utility. */
+    public static void printHistoricalLog(Context context, PrintWriter writer) {
+        final BatteryUsageHistoricalLog existingLog = parseLogFromString(
+                getSharedPreferences(context).getString(LOGS_KEY, ""));
+        final List<BatteryUsageHistoricalLogEntry> logEntryList = existingLog.getLogEntryList();
+        if (logEntryList.isEmpty()) {
+            writer.println("\tnothing to dump");
+        } else {
+            logEntryList.forEach(entry -> writer.println(toString(entry)));
+        }
+    }
+
+    @VisibleForTesting
+    static SharedPreferences getSharedPreferences(Context context) {
+        return context.getApplicationContext()
+                .getSharedPreferences(BATTERY_USAGE_FILE_NAME, Context.MODE_PRIVATE);
+    }
+
+    private static BatteryUsageHistoricalLog parseLogFromString(String storedLogs) {
+        return BatteryUtils.parseProtoFromString(
+                storedLogs, BatteryUsageHistoricalLog.getDefaultInstance());
+    }
+
+    private static String toString(BatteryUsageHistoricalLogEntry entry) {
+        final StringBuilder builder = new StringBuilder("\t")
+                .append(ConvertUtils.utcToLocalTimeForLogging(entry.getTimestamp()))
+                .append(" " + entry.getAction());
+        final String description = entry.getActionDescription();
+        if (description != null && !description.isEmpty()) {
+            builder.append(" " + description);
+        }
+        return builder.toString();
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java
index 9be378b..6d5082c 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java
@@ -39,6 +39,12 @@
     private static final Duration DUMP_TIME_OFFSET_FOR_ENTRY = Duration.ofHours(4);
 
     static void dumpBatteryUsageDatabaseHist(Context context, PrintWriter writer) {
+        // Dumps periodic job events.
+        writer.println("\nBattery PeriodicJob History:");
+        BatteryUsageLogUtils.printHistoricalLog(context, writer);
+        writer.flush();
+
+        // Dumps phenotype environments.
         DatabaseUtils.dump(context, writer);
         writer.flush();
         final BatteryStateDao dao =
@@ -47,6 +53,7 @@
                         .batteryStateDao();
         final long timeOffset =
                 Clock.systemUTC().millis() - DUMP_TIME_OFFSET.toMillis();
+
         // Gets all distinct timestamps.
         final List<Long> timestamps = dao.getDistinctTimestamps(timeOffset);
         final int distinctCount = timestamps.size();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
index a638d09..0a6de71 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
@@ -36,9 +36,16 @@
     @Query("SELECT * FROM BatteryEventEntity ORDER BY timestamp DESC")
     List<BatteryEventEntity> getAll();
 
+    /** Gets the {@link Cursor} of the last full charge time . */
+    @Query("SELECT MAX(timestamp) FROM BatteryEventEntity"
+            + " WHERE batteryEventType = 3")  // BatteryEventType.FULL_CHARGED = 3
+    Cursor getLastFullChargeTimestamp();
+
     /** Gets the {@link Cursor} of all recorded data after a specific timestamp. */
-    @Query("SELECT * FROM BatteryEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC")
-    Cursor getAllAfter(long timestamp);
+    @Query("SELECT * FROM BatteryEventEntity"
+            + " WHERE timestamp > :timestamp AND batteryEventType IN (:batteryEventTypes)"
+            + " ORDER BY timestamp DESC")
+    Cursor getAllAfter(long timestamp, List<Integer> batteryEventTypes);
 
     /** Deletes all recorded data before a specific timestamp. */
     @Query("DELETE FROM BatteryEventEntity WHERE timestamp <= :timestamp")
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
index 6d2ab8d..520c6be 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
@@ -37,16 +37,18 @@
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     void insertAll(List<BatteryState> states);
 
+    /** Gets the {@link Cursor} of the latest record timestamp no later than the given timestamp. */
+    @Query("SELECT MAX(timestamp) FROM BatteryState WHERE timestamp <= :timestamp")
+    Cursor getLatestTimestampBefore(long timestamp);
+
+    /** Lists all recorded battery states after a specific timestamp. */
+    @Query("SELECT * FROM BatteryState WHERE timestamp >= :timestamp ORDER BY timestamp ASC")
+    Cursor getBatteryStatesAfter(long timestamp);
+
     /** Lists all recorded data after a specific timestamp. */
     @Query("SELECT * FROM BatteryState WHERE timestamp > :timestamp ORDER BY timestamp DESC")
     List<BatteryState> getAllAfter(long timestamp);
 
-    /** Gets the {@link Cursor} of all recorded data since last full charge within 7 days. */
-    @Query("SELECT * FROM BatteryState WHERE timestamp >= :timestampSixDaysAgo AND timestamp >= "
-            + "(SELECT IFNULL((SELECT MAX(timestamp) FROM BatteryState "
-            + "WHERE isFullChargeCycleStart = 1), 0)) ORDER BY timestamp ASC")
-    Cursor getCursorSinceLastFullCharge(long timestampSixDaysAgo);
-
     /** Get the count of distinct timestamp after a specific timestamp. */
     @Query("SELECT COUNT(DISTINCT timestamp) FROM BatteryState WHERE timestamp > :timestamp")
     int getDistinctTimestampCount(long timestamp);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
index 466a7ca..28a0012 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
@@ -25,7 +25,8 @@
 
 /** A {@link RoomDatabase} for battery usage states history. */
 @Database(
-        entities = {AppUsageEventEntity.class, BatteryEventEntity.class, BatteryState.class},
+        entities = {AppUsageEventEntity.class, BatteryEventEntity.class, BatteryState.class,
+                BatteryUsageSlotEntity.class},
         version = 1)
 public abstract class BatteryStateDatabase extends RoomDatabase {
     private static final String TAG = "BatteryStateDatabase";
@@ -38,13 +39,15 @@
     public abstract BatteryEventDao batteryEventDao();
     /** Provides DAO for battery state table. */
     public abstract BatteryStateDao batteryStateDao();
+    /** Provides DAO for battery usage slot table. */
+    public abstract BatteryUsageSlotDao batteryUsageSlotDao();
 
     /** Gets or creates an instance of {@link RoomDatabase}. */
     public static BatteryStateDatabase getInstance(Context context) {
         if (sBatteryStateDatabase == null) {
             sBatteryStateDatabase =
                     Room.databaseBuilder(
-                                    context, BatteryStateDatabase.class, "battery-usage-db-v8")
+                                    context, BatteryStateDatabase.class, "battery-usage-db-v9")
                             // Allows accessing data in the main thread for dumping bugreport.
                             .allowMainThreadQueries()
                             .fallbackToDestructiveMigration()
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java
new file mode 100644
index 0000000..a695f6a
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import android.database.Cursor;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import java.util.List;
+
+/** Data access object for accessing {@link BatteryUsageSlotEntity} in the database. */
+@Dao
+public interface BatteryUsageSlotDao {
+    /** Inserts a {@link BatteryUsageSlotEntity} data into the database. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(BatteryUsageSlotEntity event);
+
+    /** Gets all recorded data. */
+    @Query("SELECT * FROM BatteryUsageSlotEntity ORDER BY timestamp ASC")
+    List<BatteryUsageSlotEntity> getAll();
+
+    /** Gets the {@link Cursor} of all recorded data after a specific timestamp. */
+    @Query("SELECT * FROM BatteryUsageSlotEntity WHERE timestamp >= :timestamp"
+            + " ORDER BY timestamp ASC")
+    Cursor getAllAfter(long timestamp);
+
+    /** Deletes all recorded data before a specific timestamp. */
+    @Query("DELETE FROM BatteryUsageSlotEntity WHERE timestamp <= :timestamp")
+    void clearAllBefore(long timestamp);
+
+    /** Clears all recorded data in the database. */
+    @Query("DELETE FROM BatteryUsageSlotEntity")
+    void clearAll();
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java
new file mode 100644
index 0000000..c2d5631
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import android.content.ContentValues;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.util.Locale;
+
+/** A {@link Entity} class to save battery usage slot into database. */
+@Entity
+public class BatteryUsageSlotEntity {
+    /** Keys for accessing {@link ContentValues}. */
+    public static final String KEY_TIMESTAMP = "timestamp";
+    public static final String KEY_BATTERY_USAGE_SLOT = "batteryUsageSlot";
+
+    @PrimaryKey(autoGenerate = true)
+    private long mId;
+
+    public final long timestamp;
+    public final String batteryUsageSlot;
+
+    public BatteryUsageSlotEntity(final long timestamp, final String batteryUsageSlot) {
+        this.timestamp = timestamp;
+        this.batteryUsageSlot = batteryUsageSlot;
+    }
+
+    /** Sets the auto-generated content ID. */
+    public void setId(long id) {
+        this.mId = id;
+    }
+
+    /** Gets the auto-generated content ID. */
+    public long getId() {
+        return mId;
+    }
+
+    @Override
+    public String toString() {
+        final String recordAtDateTime = ConvertUtils.utcToLocalTimeForLogging(timestamp);
+        final StringBuilder builder = new StringBuilder()
+                .append("\nBatteryUsageSlot{")
+                .append(String.format(Locale.US, "\n\ttimestamp=%s|batteryUsageSlot=%s",
+                        recordAtDateTime, batteryUsageSlot))
+                .append("\n}");
+        return builder.toString();
+    }
+
+    /** Creates new {@link BatteryUsageSlotEntity} from {@link ContentValues}. */
+    public static BatteryUsageSlotEntity create(ContentValues contentValues) {
+        Builder builder = BatteryUsageSlotEntity.newBuilder();
+        if (contentValues.containsKey(KEY_TIMESTAMP)) {
+            builder.setTimestamp(contentValues.getAsLong(KEY_TIMESTAMP));
+        }
+        if (contentValues.containsKey(KEY_BATTERY_USAGE_SLOT)) {
+            builder.setBatteryUsageSlot(contentValues.getAsString(KEY_BATTERY_USAGE_SLOT));
+        }
+        return builder.build();
+    }
+
+    /** Creates a new {@link Builder} instance. */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** A convenience builder class to improve readability. */
+    public static class Builder {
+        private long mTimestamp;
+        private String mBatteryUsageSlot;
+
+        /** Sets the timestamp. */
+        @CanIgnoreReturnValue
+        public Builder setTimestamp(final long timestamp) {
+            mTimestamp = timestamp;
+            return this;
+        }
+
+        /** Sets the battery usage slot. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryUsageSlot(final String batteryUsageSlot) {
+            mBatteryUsageSlot = batteryUsageSlot;
+            return this;
+        }
+
+        /** Builds the {@link BatteryUsageSlotEntity}. */
+        public BatteryUsageSlotEntity build() {
+            return new BatteryUsageSlotEntity(mTimestamp, mBatteryUsageSlot);
+        }
+
+        private Builder() {}
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/protos/Android.bp b/src/com/android/settings/fuelgauge/protos/Android.bp
index 3af2aef..531bdc32 100644
--- a/src/com/android/settings/fuelgauge/protos/Android.bp
+++ b/src/com/android/settings/fuelgauge/protos/Android.bp
@@ -24,9 +24,25 @@
 }
 
 java_library {
+    name: "battery-usage-slot-protos-lite",
+    proto: {
+        type: "lite",
+    },
+    srcs: ["battery_usage_slot.proto"],
+}
+
+java_library {
     name: "fuelgauge-usage-state-protos-lite",
     proto: {
         type: "lite",
     },
     srcs: ["fuelgauge_usage_state.proto"],
 }
+
+java_library {
+    name: "power-anomaly-event-protos-lite",
+    proto: {
+        type: "lite",
+    },
+    srcs: ["power_anomaly_event.proto"],
+}
diff --git a/src/com/android/settings/fuelgauge/protos/battery_event.proto b/src/com/android/settings/fuelgauge/protos/battery_event.proto
index 80ccb3b..58ab3be 100644
--- a/src/com/android/settings/fuelgauge/protos/battery_event.proto
+++ b/src/com/android/settings/fuelgauge/protos/battery_event.proto
@@ -8,6 +8,8 @@
   UNKNOWN_EVENT = 0;
   POWER_CONNECTED = 1;
   POWER_DISCONNECTED = 2;
+  FULL_CHARGED = 3;
+  EVEN_HOUR = 4;
 }
 
 message BatteryEvent {
diff --git a/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto
new file mode 100644
index 0000000..e3b604b
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/protos/battery_usage_slot.proto
@@ -0,0 +1,32 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.android.settings.fuelgauge.batteryusage";
+option java_outer_classname = "BatteryUsageSlotProto";
+
+message BatteryUsageSlot {
+  optional int64 start_timestamp = 1;
+  optional int64 end_timestamp = 2;
+  optional int32 start_battery_level = 3;
+  optional int32 end_battery_level = 4;
+  optional int64 screen_on_time = 5;
+  repeated BatteryUsageDiff app_usage = 6;
+  repeated BatteryUsageDiff system_usage = 7;
+}
+
+message BatteryUsageDiff {
+  optional int64 uid = 1;
+  optional int64 user_id = 2;
+  optional string package_name = 3;
+  optional string label = 4;
+  optional string key = 5;
+  optional bool is_hidden = 6;
+  optional int32 component_id = 7;
+  optional int32 consumer_type = 8;
+  optional double consume_power = 9;
+  optional double foreground_usage_consume_power = 10;
+  optional double background_usage_consume_power = 11;
+  optional int64 foreground_usage_time = 12;
+  optional int64 background_usage_time = 13;
+  optional int64 screen_on_time = 14;
+}
diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
new file mode 100644
index 0000000..c799d7a
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -0,0 +1,65 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.android.settings.fuelgauge.batteryusage";
+option java_outer_classname = "PowerAnomalyEventProto";
+
+message PowerAnomalyEventList {
+  repeated PowerAnomalyEvent power_anomaly_events = 1;
+}
+
+message PowerAnomalyEvent {
+  optional string event_id = 1;
+  optional int64 timestamp = 2;
+  optional PowerAnomalyType type = 3;
+  optional PowerAnomalyKey key = 4;
+  optional float score = 5;
+  oneof info {
+    WarningBannerInfo warning_banner_info = 6;
+    WarningItemInfo warning_item_info = 7;
+  }
+}
+
+// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
+// The enum value will be used to decide the tips card style like icons and colors.
+//
+// Next id: 2
+enum PowerAnomalyType{
+  TYPE_SETTINGS_BANNER = 0;
+  TYPE_APPS_ITEM = 1;
+}
+
+// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
+// The enum value will be used to decide pre-defined title and button labels.
+//
+// Next id: 3
+enum PowerAnomalyKey{
+  KEY_BRIGHTNESS = 0;
+  KEY_SCREEN_TIMEOUT = 1;
+  KEY_APP = 2;
+}
+
+message WarningBannerInfo {
+  optional string title_string = 1;
+  optional string description_string = 2;
+  optional string main_button_string = 3;
+  // Used in the SubSettingLauncher.setDestination().
+  optional string main_button_destination = 4;
+  // Used in the SubSettingLauncher.setSourceMetricsCategory().
+  optional int32 main_button_source_metrics_category = 5;
+  optional string cancel_button_string = 6;
+}
+
+message WarningItemInfo {
+  optional int64 start_timestamp = 1;
+  optional int64 end_timestamp = 2;
+  optional string top_card_string = 3;
+  optional string title_string = 4;
+  optional string description_string = 5;
+  optional string main_button_string = 6;
+  // Used in the SubSettingLauncher.setDestination().
+  optional string main_button_destination = 7;
+  // Used in the SubSettingLauncher.setSourceMetricsCategory().
+  optional int32 main_button_source_metrics_category = 8;
+  optional string cancel_button_string = 9;
+}
diff --git a/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProvider.java b/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProvider.java
new file mode 100644
index 0000000..7255107
--- /dev/null
+++ b/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceScreen;
+
+/**
+ * Provider for Keyboard settings related features.
+ */
+public interface KeyboardSettingsFeatureProvider {
+
+    /**
+     * Checks whether the connected device supports firmware update.
+     *
+     * @return true if the connected device supports firmware update.
+     */
+    boolean supportsFirmwareUpdate();
+
+    /**
+     * Add firmware update preference category .
+     *
+     * @param context The context to initialize the application with.
+     * @param screen  The {@link PreferenceScreen} to add the firmware update preference category.
+     *
+     * @return true if the category is added successfully.
+     */
+    boolean addFirmwareUpdateCategory(Context context, PreferenceScreen screen);
+
+    /**
+     * Get custom action key icon.
+     *
+     * @param context Context for accessing resources.
+     *
+     * @return Returns the image of the icon, or null if there is no any custom icon.
+     */
+    @Nullable
+    Drawable getActionKeyIcon(Context context);
+}
diff --git a/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProviderImpl.java b/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProviderImpl.java
new file mode 100644
index 0000000..26b10e5
--- /dev/null
+++ b/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProviderImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.preference.PreferenceScreen;
+
+/**
+ * Provider implementation for keyboard settings related features.
+ */
+public class KeyboardSettingsFeatureProviderImpl implements KeyboardSettingsFeatureProvider {
+
+    @Override
+    public boolean supportsFirmwareUpdate() {
+        return false;
+    }
+
+    @Override
+    public boolean addFirmwareUpdateCategory(Context context, PreferenceScreen screen) {
+        return false;
+    }
+
+    @Override
+    public Drawable getActionKeyIcon(Context context) {
+        return null;
+    };
+}
diff --git a/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java b/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java
index 03461af..ae6a24a 100644
--- a/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java
+++ b/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.provider.Settings;
@@ -53,8 +54,7 @@
             if (mCachedDevice.getAddress().equals(hardKeyboardDeviceInfo.mBluetoothAddress)) {
                 Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
                 intent.putExtra(
-                        NewKeyboardSettingsUtils.EXTRA_INTENT_FROM,
-                        "com.android.settings.inputmethod.KeyboardSettingsPreferenceController");
+                        Settings.EXTRA_ENTRYPOINT, SettingsEnums.CONNECTED_DEVICES_SETTINGS);
                 intent.putExtra(
                         Settings.EXTRA_INPUT_DEVICE_IDENTIFIER,
                         hardKeyboardDeviceInfo.mDeviceIdentifier);
diff --git a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
index 949e656..28ead89 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysPickerDialogFragment.java
@@ -21,7 +21,9 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.text.Spannable;
@@ -39,11 +41,14 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.fragment.app.DialogFragment;
 import androidx.preference.Preference;
 
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.Utils;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -60,6 +65,12 @@
     private String mKeyDefaultName;
     private String mKeyFocus;
     private Activity mActivity;
+    private KeyboardSettingsFeatureProvider mFeatureProvider;
+    private Drawable mActionKeyDrawable;
+    private TextView mLeftBracket;
+    private TextView mRightBracket;
+    private ImageView mActionKeyIcon;
+    private MetricsFeatureProvider mMetricsFeatureProvider;
 
     private List<int[]> mRemappableKeyList =
             new ArrayList<>(Arrays.asList(
@@ -83,6 +94,9 @@
         super.onCreateDialog(savedInstanceState);
 
         mActivity = getActivity();
+        mMetricsFeatureProvider = FeatureFactory.getFactory(mActivity).getMetricsFeatureProvider();
+        FeatureFactory featureFactory = FeatureFactory.getFactory(mActivity);
+        mFeatureProvider = featureFactory.getKeyboardSettingsFeatureProvider();
         InputManager inputManager = mActivity.getSystemService(InputManager.class);
         mKeyDefaultName = getArguments().getString(DEFAULT_KEY);
         mKeyFocus = getArguments().getString(SELECTION_KEY);
@@ -97,6 +111,10 @@
         for (int i = 0; i < modifierKeys.size(); i++) {
             mRemappableKeyMap.put(modifierKeys.get(i), mRemappableKeyList.get(i));
         }
+        Drawable drawable = mFeatureProvider.getActionKeyIcon(mActivity);
+        if (drawable != null) {
+            mActionKeyDrawable = DrawableCompat.wrap(drawable);
+        }
 
         View dialoglayout  =
                 LayoutInflater.from(mActivity).inflate(R.layout.modifier_key_picker_dialog, null);
@@ -125,6 +143,7 @@
         doneButton.setOnClickListener(v -> {
             String selectedItem = modifierKeys.get(adapter.getCurrentItem());
             Spannable itemSummary;
+            logMetricsForRemapping(selectedItem);
             if (selectedItem.equals(mKeyDefaultName)) {
                 itemSummary = new SpannableString(
                         mActivity.getString(R.string.modifier_keys_default_summary));
@@ -175,6 +194,28 @@
         return modifierKeyDialog;
     }
 
+    private void logMetricsForRemapping(String selectedItem) {
+        if (mKeyDefaultName.equals("Caps lock")) {
+            mMetricsFeatureProvider.action(
+                    mActivity, SettingsEnums.ACTION_FROM_CAPS_LOCK_TO, selectedItem);
+        }
+
+        if (mKeyDefaultName.equals("Ctrl")) {
+            mMetricsFeatureProvider.action(
+                    mActivity, SettingsEnums.ACTION_FROM_CTRL_TO, selectedItem);
+        }
+
+        if (mKeyDefaultName.equals("Action key")) {
+            mMetricsFeatureProvider.action(
+                    mActivity, SettingsEnums.ACTION_FROM_ACTION_KEY_TO, selectedItem);
+        }
+
+        if (mKeyDefaultName.equals("Alt")) {
+            mMetricsFeatureProvider.action(
+                    mActivity, SettingsEnums.ACTION_FROM_ALT_TO, selectedItem);
+        }
+    }
+
     private void setInitialFocusItem(
             List<String> modifierKeys, ModifierKeyAdapter adapter) {
         if (modifierKeys.indexOf(mKeyFocus) == -1) {
@@ -226,10 +267,18 @@
                 checkIcon.setImageAlpha(255);
                 view.setBackground(
                         mActivity.getDrawable(R.drawable.modifier_key_lisetview_background));
+                if (mActionKeyDrawable != null && i == 2) {
+                    setActionKeyIcon(view);
+                    setActionKeyColor(getColorOfMaterialColorPrimary());
+                }
             } else {
                 textView.setTextColor(getColorOfTextColorPrimary());
                 checkIcon.setImageAlpha(0);
                 view.setBackground(null);
+                if (mActionKeyDrawable != null && i == 2) {
+                    setActionKeyIcon(view);
+                    setActionKeyColor(getColorOfTextColorPrimary());
+                }
             }
             return view;
         }
@@ -243,6 +292,21 @@
         }
     }
 
+    private void setActionKeyIcon(View view) {
+        mLeftBracket = view.findViewById(R.id.modifier_key_left_bracket);
+        mRightBracket = view.findViewById(R.id.modifier_key_right_bracket);
+        mActionKeyIcon = view.findViewById(R.id.modifier_key_action_key_icon);
+        mLeftBracket.setText("(");
+        mRightBracket.setText(")");
+        mActionKeyIcon.setImageDrawable(mActionKeyDrawable);
+    }
+
+    private void setActionKeyColor(int color) {
+        mLeftBracket.setTextColor(color);
+        mRightBracket.setTextColor(color);
+        DrawableCompat.setTint(mActionKeyDrawable, color);
+    }
+
     private int getColorOfTextColorPrimary() {
         return Utils.getColorAttrDefaultColor(mActivity, android.R.attr.textColorPrimary);
     }
diff --git a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
index 5d8149a..77def48 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysPreferenceController.java
@@ -17,12 +17,16 @@
 package com.android.settings.inputmethod;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.style.ForegroundColorSpan;
+import android.util.Pair;
 import android.view.KeyEvent;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
@@ -31,7 +35,9 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.Utils;
+import com.android.settingslib.widget.LayoutPreference;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -53,6 +59,7 @@
     private FragmentManager mFragmentManager;
     private final InputManager mIm;
     private PreferenceScreen mScreen;
+    private Drawable mDrawable;
 
     private final List<Integer> mRemappableKeys = new ArrayList<>(
             Arrays.asList(
@@ -61,6 +68,14 @@
                     KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
                     KeyEvent.KEYCODE_CAPS_LOCK));
 
+    private final List<Pair<String, Integer>> mKeys = new ArrayList<>(
+            Arrays.asList(
+                    Pair.create(KEY_PREFERENCE_CTRL, R.string.modifier_keys_ctrl),
+                    Pair.create(KEY_PREFERENCE_META, R.string.modifier_keys_meta),
+                    Pair.create(KEY_PREFERENCE_ALT, R.string.modifier_keys_alt),
+                    Pair.create(KEY_PREFERENCE_CAPS_LOCK, R.string.modifier_keys_caps_lock)
+            ));
+
     private String[] mKeyNames = new String[] {
             mContext.getString(R.string.modifier_keys_ctrl),
             mContext.getString(R.string.modifier_keys_ctrl),
@@ -74,6 +89,9 @@
         super(context, key);
         mIm = context.getSystemService(InputManager.class);
         Objects.requireNonNull(mIm, "InputManager service cannot be null");
+        KeyboardSettingsFeatureProvider featureProvider =
+                FeatureFactory.getFactory(context).getKeyboardSettingsFeatureProvider();
+        mDrawable = featureProvider.getActionKeyIcon(context);
     }
 
     public void setFragment(Fragment parent) {
@@ -91,33 +109,59 @@
     }
 
     private void refreshUi() {
+        initDefaultKeysName();
         for (Map.Entry<Integer, Integer> entry : mIm.getModifierKeyRemapping().entrySet()) {
             int fromKey = entry.getKey();
             int toKey = entry.getValue();
             int index = mRemappableKeys.indexOf(toKey);
 
             if (isCtrl(fromKey) && mRemappableKeys.contains(toKey)) {
-                Preference preference = mScreen.findPreference(KEY_PREFERENCE_CTRL);
-                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+                setSummaryColor(KEY_PREFERENCE_CTRL, index);
             }
 
             if (isMeta(fromKey) && mRemappableKeys.contains(toKey)) {
-                Preference preference = mScreen.findPreference(KEY_PREFERENCE_META);
-                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+                setSummaryColor(KEY_PREFERENCE_META, index);
             }
 
             if (isAlt(fromKey) && mRemappableKeys.contains(toKey)) {
-                Preference preference = mScreen.findPreference(KEY_PREFERENCE_ALT);
-                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+                setSummaryColor(KEY_PREFERENCE_ALT, index);
             }
 
             if (isCapLock(fromKey) && mRemappableKeys.contains(toKey)) {
-                Preference preference = mScreen.findPreference(KEY_PREFERENCE_CAPS_LOCK);
-                preference.setSummary(changeSummaryColor(mKeyNames[index]));
+                setSummaryColor(KEY_PREFERENCE_CAPS_LOCK, index);
             }
         }
     }
 
+    private void initDefaultKeysName() {
+        for (Pair<String, Integer> key : mKeys) {
+            LayoutPreference layoutPreference = mScreen.findPreference(key.first);
+            TextView title = layoutPreference.findViewById(R.id.title);
+            TextView summary = layoutPreference.findViewById(R.id.summary);
+            title.setText(key.second);
+            summary.setText(R.string.modifier_keys_default_summary);
+
+            if (key.first.equals(KEY_PREFERENCE_META) && mDrawable != null) {
+                setActionKeyIcon(layoutPreference, mDrawable);
+            }
+        }
+    }
+
+    private static void setActionKeyIcon(LayoutPreference preference, Drawable drawable) {
+        TextView leftBracket = preference.findViewById(R.id.modifier_key_left_bracket);
+        TextView rightBracket = preference.findViewById(R.id.modifier_key_right_bracket);
+        ImageView actionKeyIcon = preference.findViewById(R.id.modifier_key_action_key_icon);
+        leftBracket.setText("(");
+        rightBracket.setText(")");
+        actionKeyIcon.setImageDrawable(drawable);
+    }
+
+    private void setSummaryColor(String key, int targetIndex) {
+        LayoutPreference layoutPreference = mScreen.findPreference(key);
+        TextView summary = layoutPreference.findViewById(R.id.summary);
+        summary.setText(changeSummaryColor(mKeyNames[targetIndex]));
+    }
+
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
         if (preference.getKey().equals(KEY_RESTORE_PREFERENCE)) {
@@ -137,12 +181,14 @@
         ModifierKeysPickerDialogFragment fragment = new ModifierKeysPickerDialogFragment();
         fragment.setTargetFragment(mParent, 0);
         Bundle bundle = new Bundle();
+        TextView title = ((LayoutPreference) preference).findViewById(R.id.title);
+        TextView summary = ((LayoutPreference) preference).findViewById(R.id.summary);
         bundle.putString(
                 ModifierKeysPickerDialogFragment.DEFAULT_KEY,
-                preference.getTitle().toString());
+                title.getText().toString());
         bundle.putString(
                 ModifierKeysPickerDialogFragment.SELECTION_KEY,
-                preference.getSummary().toString());
+                summary.getText().toString());
         fragment.setArguments(bundle);
         fragment.show(mFragmentManager, KEY_TAG);
     }
diff --git a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
index 755e9dd..fea6e65 100644
--- a/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
+++ b/src/com/android/settings/inputmethod/ModifierKeysResetDialogFragment.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.app.settings.SettingsEnums;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.view.LayoutInflater;
@@ -31,13 +32,18 @@
 import androidx.fragment.app.DialogFragment;
 
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class ModifierKeysResetDialogFragment extends DialogFragment {
+
     private static final String MODIFIER_KEYS_CAPS_LOCK = "modifier_keys_caps_lock";
     private static final String MODIFIER_KEYS_CTRL = "modifier_keys_ctrl";
     private static final String MODIFIER_KEYS_META = "modifier_keys_meta";
     private static final String MODIFIER_KEYS_ALT = "modifier_keys_alt";
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     private String[] mKeys = {
             MODIFIER_KEYS_CAPS_LOCK,
             MODIFIER_KEYS_CTRL,
@@ -51,6 +57,7 @@
         super.onCreateDialog(savedInstanceState);
 
         Activity activity = getActivity();
+        mMetricsFeatureProvider = FeatureFactory.getFactory(activity).getMetricsFeatureProvider();
         InputManager inputManager = activity.getSystemService(InputManager.class);
         View dialoglayout =
                 LayoutInflater.from(activity).inflate(R.layout.modifier_key_reset_dialog, null);
@@ -60,6 +67,7 @@
 
         Button restoreButton = dialoglayout.findViewById(R.id.modifier_key_reset_restore_button);
         restoreButton.setOnClickListener(v -> {
+            mMetricsFeatureProvider.action(activity, SettingsEnums.ACTION_CLEAR_REMAPPINGS);
             inputManager.clearAllModifierKeyRemappings();
             dismiss();
             activity.recreate();
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerContent.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerContent.java
index 1af001b..11740ec 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerContent.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerContent.java
@@ -20,10 +20,6 @@
 import android.content.Context;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardLayout;
-import android.os.Bundle;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -32,55 +28,23 @@
 
     private static final String TAG = "KeyboardLayoutPicker";
 
-    private InputManager mIm;
-    private int mUserId;
-    private InputDeviceIdentifier mIdentifier;
-    private InputMethodInfo mInputMethodInfo;
-    private InputMethodSubtype mInputMethodSubtype;
-
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
-        mIm = getContext().getSystemService(InputManager.class);
-        Bundle arguments = getArguments();
-        final CharSequence title = arguments.getCharSequence(NewKeyboardSettingsUtils.EXTRA_TITLE);
-        mUserId = arguments.getInt(NewKeyboardSettingsUtils.EXTRA_USER_ID);
-        mIdentifier =
-                arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER);
-        mInputMethodInfo =
-                arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_METHOD_INFO);
-        mInputMethodSubtype =
-                arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE);
-        if (mIdentifier == null
-                || NewKeyboardSettingsUtils.getInputDevice(mIm, mIdentifier) == null) {
+        InputManager im = getContext().getSystemService(InputManager.class);
+        InputDeviceIdentifier identifier =
+                getArguments().getParcelable(
+                        NewKeyboardSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER);
+        if (identifier == null
+                || NewKeyboardSettingsUtils.getInputDevice(im, identifier) == null) {
             getActivity().finish();
             return;
         }
-        getActivity().setTitle(title);
-        use(NewKeyboardLayoutPickerController.class).initialize(this /*parent*/, mUserId,
-                mIdentifier, mInputMethodInfo, mInputMethodSubtype, getSelectedLayoutLabel());
-    }
-
-    private String getSelectedLayoutLabel() {
-        String label = getContext().getString(R.string.keyboard_default_layout);
-        String layout = NewKeyboardSettingsUtils.getKeyboardLayout(
-                mIm, mUserId, mIdentifier, mInputMethodInfo, mInputMethodSubtype);
-        KeyboardLayout[] keyboardLayouts = NewKeyboardSettingsUtils.getKeyboardLayouts(
-                mIm, mUserId, mIdentifier, mInputMethodInfo, mInputMethodSubtype);
-        if (layout != null) {
-            for (int i = 0; i < keyboardLayouts.length; i++) {
-                if (keyboardLayouts[i].getDescriptor().equals(layout)) {
-                    label = keyboardLayouts[i].getLabel();
-                    break;
-                }
-            }
-        }
-        return label;
+        use(NewKeyboardLayoutPickerController.class).initialize(this);
     }
 
     @Override
     public int getMetricsCategory() {
-        // TODO: add new SettingsEnums SETTINGS_KEYBOARDS_LAYOUT_PICKER_CONTENT
         return SettingsEnums.SETTINGS_KEYBOARDS_LAYOUT_PICKER;
     }
 
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
index 8278be8..65b1c62 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerController.java
@@ -16,10 +16,12 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
+import android.os.Bundle;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -27,8 +29,11 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.TickButtonPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -38,38 +43,47 @@
 
 public class NewKeyboardLayoutPickerController extends BasePreferenceController implements
         InputManager.InputDeviceListener, LifecycleObserver, OnStart, OnStop {
+
     private final InputManager mIm;
     private final Map<TickButtonPreference, KeyboardLayout> mPreferenceMap;
-
     private Fragment mParent;
+    private CharSequence mTitle;
     private int mInputDeviceId;
     private int mUserId;
     private InputDeviceIdentifier mInputDeviceIdentifier;
     private InputMethodInfo mInputMethodInfo;
     private InputMethodSubtype mInputMethodSubtype;
-
     private KeyboardLayout[] mKeyboardLayouts;
     private PreferenceScreen mScreen;
     private String mPreviousSelection;
+    private String mFinalSelectedLayout;
     private String mLayout;
+    private MetricsFeatureProvider mMetricsFeatureProvider;
 
     public NewKeyboardLayoutPickerController(Context context, String key) {
         super(context, key);
         mIm = context.getSystemService(InputManager.class);
         mInputDeviceId = -1;
         mPreferenceMap = new HashMap<>();
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
-    public void initialize(Fragment parent, int userId, InputDeviceIdentifier inputDeviceIdentifier,
-            InputMethodInfo imeInfo, InputMethodSubtype imeSubtype, String layout) {
+    public void initialize(Fragment parent) {
         mParent = parent;
-        mUserId = userId;
-        mInputDeviceIdentifier = inputDeviceIdentifier;
-        mInputMethodInfo = imeInfo;
-        mInputMethodSubtype = imeSubtype;
-        mLayout = layout;
+        Bundle arguments = parent.getArguments();
+        mTitle = arguments.getCharSequence(NewKeyboardSettingsUtils.EXTRA_TITLE);
+        mUserId = arguments.getInt(NewKeyboardSettingsUtils.EXTRA_USER_ID);
+        mInputDeviceIdentifier =
+                arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER);
+        mInputMethodInfo =
+                arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_METHOD_INFO);
+        mInputMethodSubtype =
+                arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE);
+        mLayout = getSelectedLayoutLabel();
+        mFinalSelectedLayout = mLayout;
         mKeyboardLayouts = mIm.getKeyboardLayoutListForInputDevice(
-                inputDeviceIdentifier, userId, imeInfo, imeSubtype);
+                mInputDeviceIdentifier, mUserId, mInputMethodInfo, mInputMethodSubtype);
+        parent.getActivity().setTitle(mTitle);
     }
 
     @Override
@@ -85,6 +99,11 @@
 
     @Override
     public void onStop() {
+        if (!mLayout.equals(mFinalSelectedLayout)) {
+            String change = "From:" + mLayout + ", to:" + mFinalSelectedLayout;
+            mMetricsFeatureProvider.action(
+                    mContext, SettingsEnums.ACTION_PK_LAYOUT_CHANGED, change);
+        }
         mIm.unregisterInputDeviceListener(this);
         mInputDeviceId = -1;
     }
@@ -115,6 +134,7 @@
         }
         setLayout(pref);
         mPreviousSelection = preference.getKey();
+        mFinalSelectedLayout = pref.getTitle().toString();
         return true;
     }
 
@@ -162,4 +182,21 @@
                 mInputMethodSubtype,
                 mPreferenceMap.get(preference).getDescriptor());
     }
+
+    private String getSelectedLayoutLabel() {
+        String label = mContext.getString(R.string.keyboard_default_layout);
+        String layout = NewKeyboardSettingsUtils.getKeyboardLayout(
+                mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype);
+        KeyboardLayout[] keyboardLayouts = NewKeyboardSettingsUtils.getKeyboardLayouts(
+                mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype);
+        if (layout != null) {
+            for (KeyboardLayout keyboardLayout : keyboardLayouts) {
+                if (keyboardLayout.getDescriptor().equals(layout)) {
+                    label = keyboardLayout.getLabel();
+                    break;
+                }
+            }
+        }
+        return label;
+    }
 }
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerTitle.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerTitle.java
index abcad27..7f87826 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerTitle.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutPickerTitle.java
@@ -34,8 +34,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: add new SettingsEnums SETTINGS_KEYBOARDS_LAYOUT_PICKER_TITLE
-        return SettingsEnums.SETTINGS_KEYBOARDS_LAYOUT_PICKER;
+        return SettingsEnums.SETTINGS_KEYBOARDS_LAYOUT_PICKER_TITLE;
     }
 
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java b/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java
index 697c0f0..ad68c43 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java
@@ -33,12 +33,6 @@
  */
 public class NewKeyboardSettingsUtils {
 
-    /**
-     * Record the class name of the intent sender for metrics.
-     */
-    public static final String EXTRA_INTENT_FROM =
-            "com.android.settings.inputmethod.EXTRA_INTENT_FROM";
-
     static final String EXTRA_TITLE = "keyboard_layout_picker_title";
     static final String EXTRA_USER_ID = "user_id";
     static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier";
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 936de38..289d7c1 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -48,6 +48,7 @@
 import com.android.settings.Settings;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.utils.ThreadUtils;
@@ -75,6 +76,7 @@
     private InputManager mIm;
     private InputMethodManager mImm;
     private InputDeviceIdentifier mAutoInputDeviceIdentifier;
+    private KeyboardSettingsFeatureProvider mFeatureProvider;
     @NonNull
     private PreferenceCategory mKeyboardAssistanceCategory;
     @NonNull
@@ -82,6 +84,7 @@
 
     private Intent mIntentWaitingForResult;
     private boolean mIsNewKeyboardSettings;
+    private boolean mSupportsFirmwareUpdate;
 
     static final String EXTRA_BT_ADDRESS = "extra_bt_address";
     private String mBluetoothAddress;
@@ -104,6 +107,13 @@
                 (SwitchPreference) mKeyboardAssistanceCategory.findPreference(
                         SHOW_VIRTUAL_KEYBOARD_SWITCH));
 
+        FeatureFactory featureFactory = FeatureFactory.getFactory(getContext());
+        mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider();
+        mFeatureProvider = featureFactory.getKeyboardSettingsFeatureProvider();
+        mSupportsFirmwareUpdate = mFeatureProvider.supportsFirmwareUpdate();
+        if (mSupportsFirmwareUpdate) {
+            mFeatureProvider.addFirmwareUpdateCategory(getContext(), getPreferenceScreen());
+        }
         mIsNewKeyboardSettings = FeatureFlagUtils.isEnabled(
                 getContext(), FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
         boolean isModifierKeySettingsEnabled = FeatureFlagUtils
@@ -113,7 +123,12 @@
         }
         InputDeviceIdentifier inputDeviceIdentifier = activity.getIntent().getParcelableExtra(
                 KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
-        // TODO (b/271391879): The EXTRA_INTENT_FROM is used for the future metrics.
+        int intentFromWhere =
+                activity.getIntent().getIntExtra(android.provider.Settings.EXTRA_ENTRYPOINT, -1);
+        if (intentFromWhere != -1) {
+            mMetricsFeatureProvider.action(
+                    getContext(), SettingsEnums.ACTION_OPEN_PK_SETTINGS_FROM, intentFromWhere);
+        }
         if (inputDeviceIdentifier != null) {
             mAutoInputDeviceIdentifier = inputDeviceIdentifier;
         }
@@ -244,9 +259,16 @@
                         });
             }
             category.addPreference(pref);
+            mMetricsFeatureProvider.action(
+                    getContext(),
+                    SettingsEnums.ACTION_USE_SPECIFIC_KEYBOARD,
+                    hardKeyboardDeviceInfo.mDeviceName);
         }
         mKeyboardAssistanceCategory.setOrder(1);
         preferenceScreen.addPreference(mKeyboardAssistanceCategory);
+        if (mSupportsFirmwareUpdate) {
+            mFeatureProvider.addFirmwareUpdateCategory(getPrefContext(), preferenceScreen);
+        }
         updateShowVirtualKeyboardSwitch();
     }
 
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java b/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java
index 1f01b98..b88928c 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.input.InputManager;
@@ -66,9 +67,7 @@
             return false;
         }
         Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
-        intent.putExtra(
-                NewKeyboardSettingsUtils.EXTRA_INTENT_FROM,
-                "com.android.settings.inputmethod.PhysicalKeyboardPreferenceController");
+        intent.putExtra(Settings.EXTRA_ENTRYPOINT, SettingsEnums.KEYBOARD_SETTINGS);
         mContext.startActivity(intent);
         return true;
     }
diff --git a/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java b/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java
index 7efa637..f0ee1fd 100644
--- a/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TouchGesturesButtonPreferenceController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.util.FeatureFlagUtils;
 
@@ -23,6 +24,8 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.widget.ButtonPreference;
 
 public class TouchGesturesButtonPreferenceController extends BasePreferenceController {
@@ -33,9 +36,11 @@
     private static final String GESTURE_DIALOG_TAG = "GESTURE_DIALOG_TAG";
 
     private Fragment mParent;
+    private MetricsFeatureProvider mMetricsFeatureProvider;
 
     public TouchGesturesButtonPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     public void setFragment(Fragment parent) {
@@ -63,12 +68,11 @@
 
     @Override
     public int getAvailabilityStatus() {
-        boolean touchGestureDeveloperMode = FeatureFlagUtils
-                .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
-        return touchGestureDeveloperMode ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+        return AVAILABLE;
     }
 
     private void showTouchpadGestureEducation() {
+        mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_LEARN_TOUCHPAD_GESTURE_CLICK);
         TrackpadGestureDialogFragment fragment = new TrackpadGestureDialogFragment();
         fragment.setTargetFragment(mParent, 0);
         fragment.show(mParent.getActivity().getSupportFragmentManager(), GESTURE_DIALOG_TAG);
diff --git a/src/com/android/settings/inputmethod/TrackpadBottomPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadBottomPreferenceController.java
index 5133d04..1cf1f6f 100644
--- a/src/com/android/settings/inputmethod/TrackpadBottomPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadBottomPreferenceController.java
@@ -16,16 +16,22 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.hardware.input.InputSettings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadBottomPreferenceController extends TogglePreferenceController {
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadBottomPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -36,6 +42,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         InputSettings.setTouchpadRightClickZone(mContext, isChecked);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_BOTTOM_RIGHT_TAP_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceController.java
index 017689d..11d7cf3 100644
--- a/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceController.java
@@ -16,18 +16,24 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadGoBackPreferenceController extends TogglePreferenceController {
 
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_BACK_ENABLED;
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadGoBackPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -38,6 +44,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         Settings.Secure.putInt(mContext.getContentResolver(), SETTING_KEY, isChecked ? 1 : 0);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_GO_BACK_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceController.java b/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceController.java
index 18699e3..5027e2f 100644
--- a/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceController.java
@@ -16,18 +16,24 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadGoHomePreferenceController extends TogglePreferenceController {
 
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_HOME_ENABLED;
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadGoHomePreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -38,6 +44,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         Settings.Secure.putInt(mContext.getContentResolver(), SETTING_KEY, isChecked ? 1 : 0);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_GO_HOME_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceController.java
index 21f04a3..0fb28d7 100644
--- a/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceController.java
@@ -16,18 +16,24 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadNotificationsPreferenceController extends TogglePreferenceController {
 
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_NOTIFICATION_ENABLED;
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadNotificationsPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -38,6 +44,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         Settings.Secure.putInt(mContext.getContentResolver(), SETTING_KEY, isChecked ? 1 : 0);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_NOTIFICATION_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceController.java
index 71b4119..58b4772 100644
--- a/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceController.java
@@ -16,20 +16,25 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.hardware.input.InputSettings;
 
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.core.SliderPreferenceController;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadPointerSpeedPreferenceController extends SliderPreferenceController {
 
     private SeekBarPreference mPreference;
+    private MetricsFeatureProvider mMetricsFeatureProvider;
 
     public TrackpadPointerSpeedPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -53,6 +58,8 @@
             return false;
         }
         InputSettings.setTouchpadPointerSpeed(mContext, position);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_POINTER_SPEED_CHANGED, position);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceController.java
index eab2b33..878cbe3 100644
--- a/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceController.java
@@ -16,18 +16,24 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadRecentAppsPreferenceController extends TogglePreferenceController {
 
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_OVERVIEW_ENABLED;
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadRecentAppsPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -38,6 +44,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         Settings.Secure.putInt(mContext.getContentResolver(), SETTING_KEY, isChecked ? 1 : 0);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_RECENT_APPS_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java
index 10d3013..2b74c74 100644
--- a/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceController.java
@@ -16,16 +16,22 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.hardware.input.InputSettings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadReverseScrollingPreferenceController extends TogglePreferenceController {
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadReverseScrollingPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -36,6 +42,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         InputSettings.setTouchpadNaturalScrolling(mContext, !isChecked);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_REVERSE_SCROLLING_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceController.java
index 84de64e..cfca856 100644
--- a/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceController.java
@@ -16,18 +16,24 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadSwitchAppsPreferenceController extends TogglePreferenceController {
 
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_QUICK_SWITCH_ENABLED;
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadSwitchAppsPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -38,6 +44,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         Settings.Secure.putInt(mContext.getContentResolver(), SETTING_KEY, isChecked ? 1 : 0);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_SWITCH_APPS_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceController.java b/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceController.java
index 8655307..9ee446b 100644
--- a/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceController.java
+++ b/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceController.java
@@ -16,16 +16,22 @@
 
 package com.android.settings.inputmethod;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.hardware.input.InputSettings;
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class TrackpadTapToClickPreferenceController extends TogglePreferenceController {
 
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
     public TrackpadTapToClickPreferenceController(Context context, String key) {
         super(context, key);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     @Override
@@ -36,6 +42,8 @@
     @Override
     public boolean setChecked(boolean isChecked) {
         InputSettings.setTouchpadTapToClick(mContext, isChecked);
+        mMetricsFeatureProvider.action(
+                mContext, SettingsEnums.ACTION_GESTURE_TAP_TO_CLICK_CHANGED, isChecked);
         return true;
     }
 
diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
index 092207b..d1e1137 100644
--- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java
+++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.FragmentTransaction;
 import android.app.LocaleManager;
+import android.app.settings.SettingsEnums;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
@@ -37,15 +38,22 @@
 import com.android.settings.applications.AppLocaleUtil;
 import com.android.settings.applications.appinfo.AppLocaleDetails;
 import com.android.settings.core.SettingsBaseActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 public class AppLocalePickerActivity extends SettingsBaseActivity
         implements LocalePickerWithRegion.LocaleSelectedListener, MenuItem.OnActionExpandListener {
     private static final String TAG = AppLocalePickerActivity.class.getSimpleName();
+    private static final int SIM_LOCALE = 1 << 0;
+    private static final int SYSTEM_LOCALE = 1 << 1;
+    private static final int APP_LOCALE = 1 << 2;
+    private static final int IME_LOCALE = 1 << 3;
 
     private String mPackageName;
     private LocalePickerWithRegion mLocalePickerWithRegion;
     private AppLocaleDetails mAppLocaleDetails;
     private View mAppLocaleDetailContainer;
+    private MetricsFeatureProvider mMetricsFeatureProvider;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -71,6 +79,7 @@
 
         setTitle(R.string.app_locale_picker_title);
         getActionBar().setDisplayHomeAsUpEnabled(true);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(this).getMetricsFeatureProvider();
 
         mLocalePickerWithRegion = LocalePickerWithRegion.createLanguagePicker(
                 this,
@@ -99,6 +108,7 @@
         if (localeInfo == null || localeInfo.getLocale() == null || localeInfo.isSystemLocale()) {
             setAppDefaultLocale("");
         } else {
+            logLocaleSource(localeInfo);
             setAppDefaultLocale(localeInfo.getLocale().toLanguageTag());
         }
         finish();
@@ -177,4 +187,32 @@
 
         return false;
     }
+
+    private void logLocaleSource(LocaleStore.LocaleInfo localeInfo) {
+        if (!localeInfo.isSuggested() || localeInfo.isAppCurrentLocale()) {
+            return;
+        }
+        int localeSource = 0;
+        if (hasSuggestionType(localeInfo,
+                LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)) {
+            localeSource |= SYSTEM_LOCALE;
+        }
+        if (hasSuggestionType(localeInfo,
+                LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)) {
+            localeSource |= APP_LOCALE;
+        }
+        if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)) {
+            localeSource |= IME_LOCALE;
+        }
+        if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)) {
+            localeSource |= SIM_LOCALE;
+        }
+        mMetricsFeatureProvider.action(this,
+                SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED, localeSource);
+    }
+
+    private static boolean hasSuggestionType(LocaleStore.LocaleInfo localeInfo,
+            int suggestionType) {
+        return localeInfo.isSuggestionOfType(suggestionType);
+    }
 }
diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java
index ad9e10f..6c37e38 100644
--- a/src/com/android/settings/localepicker/LocaleDialogFragment.java
+++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.localepicker;
 
+import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
+
 import android.app.Activity;
 import android.app.Dialog;
 import android.app.settings.SettingsEnums;
@@ -23,15 +25,17 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.FragmentManager;
 
 import com.android.internal.app.LocaleStore;
 import com.android.settings.R;
@@ -53,6 +57,12 @@
     static final String ARG_SHOW_DIALOG = "arg_show_dialog";
 
     private boolean mShouldKeepDialog;
+    private AlertDialog mAlertDialog;
+    private OnBackInvokedDispatcher mBackDispatcher;
+
+    private OnBackInvokedCallback mBackCallback = () -> {
+        Log.d(TAG, "Do not back to previous page if the dialog is displaying.");
+    };
 
     public static LocaleDialogFragment newInstance() {
         return new LocaleDialogFragment();
@@ -108,9 +118,15 @@
         if (!dialogContent.mNegativeButton.isEmpty()) {
             builder.setNegativeButton(dialogContent.mNegativeButton, controller);
         }
-        AlertDialog alertDialog = builder.create();
-        alertDialog.setCanceledOnTouchOutside(false);
-        return alertDialog;
+        mAlertDialog = builder.create();
+        getOnBackInvokedDispatcher().registerOnBackInvokedCallback(PRIORITY_DEFAULT, mBackCallback);
+        mAlertDialog.setCanceledOnTouchOutside(false);
+        mAlertDialog.setOnDismissListener(dialogInterface -> {
+            mAlertDialog.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(
+                            mBackCallback);
+        });
+
+        return mAlertDialog;
     }
 
     private static void setDialogTitle(View root, String content) {
@@ -130,6 +146,25 @@
     }
 
     @VisibleForTesting
+    public OnBackInvokedCallback getBackInvokedCallback() {
+        return mBackCallback;
+    }
+
+    @VisibleForTesting
+    public void setBackDispatcher(OnBackInvokedDispatcher dispatcher) {
+        mBackDispatcher = dispatcher;
+    }
+
+    @VisibleForTesting
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        if (mBackDispatcher != null) {
+            return mBackDispatcher;
+        } else {
+            return mAlertDialog.getOnBackInvokedDispatcher();
+        }
+    }
+
+    @VisibleForTesting
     LocaleDialogController getLocaleDialogController(Context context,
             LocaleDialogFragment dialogFragment, LocaleListEditor parentFragment) {
         return new LocaleDialogController(context, dialogFragment, parentFragment);
@@ -155,24 +190,22 @@
             mParent = parentFragment;
         }
 
-        LocaleDialogController(@NonNull LocaleDialogFragment dialogFragment,
-                LocaleListEditor parent) {
-            this(dialogFragment.getContext(), dialogFragment, parent);
-        }
-
         @Override
         public void onClick(DialogInterface dialog, int which) {
             if (mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT) {
                 int result = Activity.RESULT_CANCELED;
+                boolean changed = false;
                 if (which == DialogInterface.BUTTON_POSITIVE) {
                     result = Activity.RESULT_OK;
+                    changed = true;
                 }
                 Intent intent = new Intent();
                 Bundle bundle = new Bundle();
                 bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
                 intent.putExtras(bundle);
                 mParent.onActivityResult(DIALOG_CONFIRM_SYSTEM_DEFAULT, result, intent);
-                mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE);
+                mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE,
+                        changed);
             }
             mShouldKeepDialog = false;
         }
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index edd3026..6054c59 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.localepicker;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.os.Bundle;
@@ -30,6 +31,7 @@
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.core.view.MotionEventCompat;
 import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.RecyclerView;
@@ -37,6 +39,7 @@
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.LocaleStore;
 import com.android.settings.R;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.shortcut.ShortcutsUpdateTask;
 
 import java.text.NumberFormat;
@@ -176,17 +179,33 @@
         // clear listener before setChecked() in case another item already bind to
         // current ViewHolder and checked event is triggered on stale listener mistakenly.
         checkbox.setOnCheckedChangeListener(null);
-        checkbox.setChecked(mRemoveMode ? feedItem.getChecked() : false);
+        boolean isChecked = mRemoveMode ? feedItem.getChecked() : false;
+        checkbox.setChecked(isChecked);
+        setCheckBoxDescription(dragCell, checkbox, isChecked);
+
         checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
             @Override
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 LocaleStore.LocaleInfo feedItem =
                         (LocaleStore.LocaleInfo) dragCell.getTag();
                 feedItem.setChecked(isChecked);
+                setCheckBoxDescription(dragCell, checkbox, isChecked);
             }
         });
     }
 
+    @VisibleForTesting
+    protected void setCheckBoxDescription(LocaleDragCell dragCell, CheckBox checkbox,
+            boolean isChecked) {
+        CharSequence checkedStatus = mContext.getText(
+                isChecked ? com.android.internal.R.string.checked
+                        : com.android.internal.R.string.not_checked);
+        // Talkback
+        dragCell.setStateDescription(checkedStatus);
+        // Select to Speak
+        checkbox.setContentDescription(checkedStatus);
+    }
+
     @Override
     public int getItemCount() {
         int itemCount = (null != mFeedItemList ? mFeedItemList.size() : 0);
@@ -210,6 +229,13 @@
             Log.e(TAG, String.format(Locale.US,
                     "Negative position in onItemMove %d -> %d", fromPosition, toPosition));
         }
+
+        if (fromPosition != toPosition) {
+            FeatureFactory.getFactory(mContext).getMetricsFeatureProvider()
+                    .action(mContext, SettingsEnums.ACTION_REORDER_LANGUAGE,
+                            mDragLocale.getLocale().toLanguageTag() + " move to " + toPosition);
+        }
+
         notifyItemChanged(fromPosition); // to update the numbers
         notifyItemChanged(toPosition);
         notifyItemMoved(fromPosition, toPosition);
@@ -244,8 +270,13 @@
 
     void removeChecked() {
         int itemCount = mFeedItemList.size();
+        LocaleStore.LocaleInfo localeInfo;
         for (int i = itemCount - 1; i >= 0; i--) {
-            if (mFeedItemList.get(i).getChecked()) {
+            localeInfo = mFeedItemList.get(i);
+            if (localeInfo.getChecked()) {
+                FeatureFactory.getFactory(mContext).getMetricsFeatureProvider()
+                        .action(mContext, SettingsEnums.ACTION_REMOVE_LANGUAGE,
+                                localeInfo.getLocale().toLanguageTag());
                 mFeedItemList.remove(i);
             }
         }
@@ -381,10 +412,13 @@
                 // drag locale's original position to the top.
                 mDragLocale = (LocaleStore.LocaleInfo) savedInstanceState.getSerializable(
                         CFGKEY_DRAG_LOCALE);
-                mFeedItemList.removeIf(
-                        localeInfo -> TextUtils.equals(localeInfo.getId(), mDragLocale.getId()));
-                mFeedItemList.add(0, mDragLocale);
-                notifyItemRangeChanged(0, mFeedItemList.size());
+                if (mDragLocale != null) {
+                    mFeedItemList.removeIf(
+                            localeInfo -> TextUtils.equals(localeInfo.getId(),
+                                    mDragLocale.getId()));
+                    mFeedItemList.add(0, mDragLocale);
+                    notifyItemRangeChanged(0, mFeedItemList.size());
+                }
             }
         }
     }
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index 7ec08f7..65563ad 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -104,7 +104,6 @@
 
         addPreferencesFromResource(R.xml.languages);
         final Activity activity = getActivity();
-        activity.setTitle(R.string.language_picker_title);
         mLocaleHelperPreferenceController = new LocaleHelperPreferenceController(activity);
         final PreferenceScreen screen = getPreferenceScreen();
         mLocalePickerPreference = screen.findPreference(KEY_LANGUAGES_PICKER);
@@ -200,9 +199,11 @@
             localeInfo = (LocaleStore.LocaleInfo) data.getSerializableExtra(INTENT_LOCALE_KEY);
             String preferencesTags = Settings.System.getString(
                     getContext().getContentResolver(), Settings.System.LOCALE_PREFERENCES);
-
-            mAdapter.addLocale(mayAppendUnicodeTags(localeInfo, preferencesTags));
+            localeInfo = mayAppendUnicodeTags(localeInfo, preferencesTags);
+            mAdapter.addLocale(localeInfo);
             updateVisibilityOfRemoveMenu();
+            mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_ADD_LANGUAGE,
+                    localeInfo.getLocale().toLanguageTag());
         } else if (requestCode == DIALOG_CONFIRM_SYSTEM_DEFAULT) {
             localeInfo = mAdapter.getFeedItemList().get(0);
             if (resultCode == Activity.RESULT_OK) {
@@ -215,6 +216,9 @@
                     LocaleDialogFragment localeDialogFragment = LocaleDialogFragment.newInstance();
                     localeDialogFragment.setArguments(args);
                     localeDialogFragment.show(mFragmentManager, TAG_DIALOG_NOT_AVAILABLE);
+                    mMetricsFeatureProvider.action(getContext(),
+                            SettingsEnums.ACTION_NOT_SUPPORTED_SYSTEM_LANGUAGE,
+                            localeInfo.getLocale().toLanguageTag());
                 }
             } else {
                 mAdapter.notifyListChanged(localeInfo);
@@ -318,7 +322,13 @@
                                 // to remove.
                                 mRemoveMode = false;
                                 mShowingRemoveDialog = false;
+                                LocaleStore.LocaleInfo firstLocale =
+                                        mAdapter.getFeedItemList().get(0);
                                 mAdapter.removeChecked();
+                                boolean isFirstRemoved =
+                                        firstLocale != mAdapter.getFeedItemList().get(0);
+                                showConfirmDialog(isFirstRemoved, isFirstRemoved ? firstLocale
+                                        : mAdapter.getFeedItemList().get(0));
                                 setRemoveMode(false);
                             }
                         })
@@ -358,12 +368,12 @@
         final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter);
         llm.setAutoMeasureEnabled(true);
         list.setLayoutManager(llm);
-
         list.setHasFixedSize(true);
         list.setNestedScrollingEnabled(false);
         mAdapter.setRecyclerView(list);
         list.setAdapter(mAdapter);
         list.setOnTouchListener(this);
+        list.requestFocus();
 
         mAddLanguage = layout.findViewById(R.id.add_language);
         mAddLanguage.setOnClickListener(new View.OnClickListener() {
@@ -384,22 +394,27 @@
     public boolean onTouch(View v, MotionEvent event) {
         if (event.getAction() == MotionEvent.ACTION_UP
                 || event.getAction() == MotionEvent.ACTION_CANCEL) {
-            LocaleStore.LocaleInfo localeInfo = mAdapter.getFeedItemList().get(0);
-            if (!localeInfo.getLocale().equals(LocalePicker.getLocales().get(0))) {
-                final LocaleDialogFragment localeDialogFragment =
-                        LocaleDialogFragment.newInstance();
-                Bundle args = new Bundle();
-                args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
-                args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, localeInfo);
-                localeDialogFragment.setArguments(args);
-                localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT);
-            } else {
-                mAdapter.doTheUpdate();
-            }
+            showConfirmDialog(false, mAdapter.getFeedItemList().get(0));
         }
         return false;
     }
 
+    private void showConfirmDialog(boolean isFirstRemoved, LocaleStore.LocaleInfo localeInfo) {
+        Locale currentSystemLocale = LocalePicker.getLocales().get(0);
+        if (!localeInfo.getLocale().equals(currentSystemLocale)) {
+            final LocaleDialogFragment localeDialogFragment =
+                    LocaleDialogFragment.newInstance();
+            Bundle args = new Bundle();
+            args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
+            args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE,
+                    isFirstRemoved ? LocaleStore.getLocaleInfo(currentSystemLocale) : localeInfo);
+            localeDialogFragment.setArguments(args);
+            localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT);
+        } else {
+            mAdapter.doTheUpdate();
+        }
+    }
+
     // Hide the "Remove" menu if there is only one locale in the list, show it otherwise
     // This is called when the menu is first created, and then one add / remove locale
     private void updateVisibilityOfRemoveMenu() {
diff --git a/src/com/android/settings/localepicker/LocaleRecyclerView.java b/src/com/android/settings/localepicker/LocaleRecyclerView.java
deleted file mode 100644
index 4a5f28b..0000000
--- a/src/com/android/settings/localepicker/LocaleRecyclerView.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.localepicker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-class LocaleRecyclerView extends RecyclerView {
-    public LocaleRecyclerView(Context context) {
-        super(context);
-    }
-
-    public LocaleRecyclerView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public LocaleRecyclerView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-}
diff --git a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
index 32903bd..0200e52 100644
--- a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
+++ b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
@@ -23,7 +23,6 @@
 import android.content.DialogInterface;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.RecoverySystem;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -62,7 +61,7 @@
         return new AlertDialog.Builder(getActivity())
                 .setTitle(R.string.reset_esim_title)
                 .setMessage(R.string.reset_esim_desc)
-                .setPositiveButton(R.string.erase_euicc_data_button, this)
+                .setPositiveButton(R.string.erase_sim_confirm_button, this)
                 .setNegativeButton(R.string.cancel, null)
                 .setOnDismissListener(this)
                 .create();
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 9d953bf..0cd12fe 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -23,6 +23,7 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.telephony.PhoneNumberUtils;
@@ -61,6 +62,10 @@
 public class SubscriptionUtil {
     private static final String TAG = "SubscriptionUtil";
     private static final String PROFILE_GENERIC_DISPLAY_NAME = "CARD";
+    @VisibleForTesting
+    static final String SUB_ID = "sub_id";
+    @VisibleForTesting
+    static final String KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME = "unique_subscription_displayName";
     private static List<SubscriptionInfo> sAvailableResultsForTesting;
     private static List<SubscriptionInfo> sActiveResultsForTesting;
 
@@ -265,20 +270,21 @@
         // Map of SubscriptionId to DisplayName
         final Supplier<Stream<DisplayInfo>> originalInfos =
                 () -> getAvailableSubscriptions(context)
-                .stream()
-                .filter(i -> {
-                    // Filter out null values.
-                    return (i != null && i.getDisplayName() != null);
-                })
-                .map(i -> {
-                    DisplayInfo info = new DisplayInfo();
-                    info.subscriptionInfo = i;
-                    String displayName = i.getDisplayName().toString();
-                    info.originalName = TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME)
-                            ? context.getResources().getString(R.string.sim_card)
-                            : displayName.trim();
-                    return info;
-                });
+                        .stream()
+                        .filter(i -> {
+                            // Filter out null values.
+                            return (i != null && i.getDisplayName() != null);
+                        })
+                        .map(i -> {
+                            DisplayInfo info = new DisplayInfo();
+                            info.subscriptionInfo = i;
+                            String displayName = i.getDisplayName().toString();
+                            info.originalName =
+                                    TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME)
+                                    ? context.getResources().getString(R.string.sim_card)
+                                    : displayName.trim();
+                            return info;
+                        });
 
         // TODO(goldmanj) consider using a map of DisplayName to SubscriptionInfos.
         // A Unique set of display names
@@ -292,6 +298,14 @@
         // If a display name is duplicate, append the final 4 digits of the phone number.
         // Creates a mapping of Subscription id to original display name + phone number display name
         final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
+            String cachedDisplayName = getDisplayNameFromSharedPreference(
+                    context, info.subscriptionInfo.getSubscriptionId());
+            if (!TextUtils.isEmpty(cachedDisplayName)) {
+                Log.d(TAG, "use cached display name : " + cachedDisplayName);
+                info.uniqueName = cachedDisplayName;
+                return info;
+            }
+
             if (duplicateOriginalNames.contains(info.originalName)) {
                 // This may return null, if the user cannot view the phone number itself.
                 final String phoneNumber = getBidiFormattedPhoneNumber(context,
@@ -299,15 +313,17 @@
                 String lastFourDigits = "";
                 if (phoneNumber != null) {
                     lastFourDigits = (phoneNumber.length() > 4)
-                        ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
+                            ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
                 }
-
                 if (TextUtils.isEmpty(lastFourDigits)) {
                     info.uniqueName = info.originalName;
                 } else {
                     info.uniqueName = info.originalName + " " + lastFourDigits;
+                    Log.d(TAG, "Cache display name [" + info.uniqueName + "] for sub id "
+                            + info.subscriptionInfo.getSubscriptionId());
+                    saveDisplayNameToSharedPreference(
+                            context, info.subscriptionInfo.getSubscriptionId(), info.uniqueName);
                 }
-
             } else {
                 info.uniqueName = info.originalName;
             }
@@ -371,6 +387,27 @@
         return getUniqueSubscriptionDisplayName(info.getSubscriptionId(), context);
     }
 
+
+    private static SharedPreferences getDisplayNameSharedPreferences(Context context) {
+        return context.getSharedPreferences(
+                KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME, Context.MODE_PRIVATE);
+    }
+
+    private static SharedPreferences.Editor getDisplayNameSharedPreferenceEditor(Context context) {
+        return getDisplayNameSharedPreferences(context).edit();
+    }
+
+    private static void saveDisplayNameToSharedPreference(
+            Context context, int subId, CharSequence displayName) {
+        getDisplayNameSharedPreferenceEditor(context)
+                .putString(SUB_ID + subId, String.valueOf(displayName))
+                .apply();
+    }
+
+    private static String getDisplayNameFromSharedPreference(Context context, int subid) {
+        return getDisplayNameSharedPreferences(context).getString(SUB_ID + subid, "");
+    }
+
     public static String getDisplayName(SubscriptionInfo info) {
         final CharSequence name = info.getDisplayName();
         if (name != null) {
diff --git a/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java b/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java
index 245ac83..7addb59 100644
--- a/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/AbstractMobileNetworkSettings.java
@@ -18,7 +18,6 @@
 
 import android.os.SystemClock;
 import android.text.TextUtils;
-import android.util.Log;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
@@ -66,8 +65,7 @@
 
     TelephonyStatusControlSession setTelephonyAvailabilityStatus(
             Collection<AbstractPreferenceController> listOfPrefControllers) {
-        return (new TelephonyStatusControlSession.Builder(listOfPrefControllers))
-                .build();
+        return new TelephonyStatusControlSession(listOfPrefControllers, getLifecycle());
     }
 
     @Override
diff --git a/src/com/android/settings/network/telephony/Enable2gPreferenceController.java b/src/com/android/settings/network/telephony/Enable2gPreferenceController.java
index 106aa02..03f3be4 100644
--- a/src/com/android/settings/network/telephony/Enable2gPreferenceController.java
+++ b/src/com/android/settings/network/telephony/Enable2gPreferenceController.java
@@ -119,20 +119,21 @@
         String summary;
         if (isDisabledByCarrier) {
             summary = mContext.getString(R.string.enable_2g_summary_disabled_carrier,
-                    getCarrierName());
+                    getSimCardName());
         } else {
             summary = mContext.getString(R.string.enable_2g_summary);
         }
         preference.setSummary(summary);
     }
 
-    private String getCarrierName() {
+    private String getSimCardName() {
         SubscriptionInfo subInfo = SubscriptionUtil.getSubById(mSubscriptionManager, mSubId);
         if (subInfo == null) {
             return "";
         }
-        CharSequence carrierName = subInfo.getCarrierName();
-        return TextUtils.isEmpty(carrierName) ? "" : carrierName.toString();
+        // It is the sim card name, and it should be the same name as the sim page.
+        CharSequence simCardName = subInfo.getDisplayName();
+        return TextUtils.isEmpty(simCardName) ? "" : simCardName.toString();
     }
 
     /**
diff --git a/src/com/android/settings/network/telephony/TelephonyStatusControlSession.java b/src/com/android/settings/network/telephony/TelephonyStatusControlSession.java
deleted file mode 100644
index 3716f1f..0000000
--- a/src/com/android/settings/network/telephony/TelephonyStatusControlSession.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2020 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.network.telephony;
-
-import android.util.Log;
-
-import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-/**
- * Session for controlling the status of TelephonyPreferenceController(s).
- *
- * Within this session, result of {@link BasePreferenceController#availabilityStatus()}
- * would be under control.
- */
-public class TelephonyStatusControlSession implements AutoCloseable {
-
-    private static final String LOG_TAG = "TelephonyStatusControlSS";
-
-    private Collection<AbstractPreferenceController> mControllers;
-    private Collection<Future<Boolean>> mResult = new ArrayList<>();
-
-    /**
-     * Buider of session
-     */
-    public static class Builder {
-        private Collection<AbstractPreferenceController> mControllers;
-
-        /**
-         * Constructor
-         *
-         * @param controllers is a collection of {@link AbstractPreferenceController}
-         *        which would have {@link BasePreferenceController#availabilityStatus()}
-         *        under control within this session.
-         */
-        public Builder(Collection<AbstractPreferenceController> controllers) {
-            mControllers = controllers;
-        }
-
-        /**
-         * Method to build this session.
-         * @return {@link TelephonyStatusControlSession} session been setup.
-         */
-        public TelephonyStatusControlSession build() {
-            return new TelephonyStatusControlSession(mControllers);
-        }
-    }
-
-    private TelephonyStatusControlSession(Collection<AbstractPreferenceController> controllers) {
-        mControllers = controllers;
-        controllers.forEach(prefCtrl -> mResult
-                .add(ThreadUtils.postOnBackgroundThread(() -> setupAvailabilityStatus(prefCtrl))));
-
-    }
-
-    /**
-     * Close the session.
-     *
-     * No longer control the status.
-     */
-    public void close() {
-        //check the background thread is finished then unset the status of availability.
-
-        for (Future<Boolean> result : mResult) {
-            try {
-                result.get();
-            } catch (ExecutionException | InterruptedException exception) {
-                Log.e(LOG_TAG, "setup availability status failed!", exception);
-            }
-        }
-        unsetAvailabilityStatus(mControllers);
-    }
-
-    private Boolean setupAvailabilityStatus(AbstractPreferenceController controller) {
-        try {
-            if (controller instanceof TelephonyAvailabilityHandler) {
-                int status = ((BasePreferenceController) controller)
-                        .getAvailabilityStatus();
-                ((TelephonyAvailabilityHandler) controller).setAvailabilityStatus(status);
-            }
-            return true;
-        } catch (Exception exception) {
-            Log.e(LOG_TAG, "Setup availability status failed!", exception);
-            return false;
-        }
-    }
-
-    private void unsetAvailabilityStatus(
-            Collection<AbstractPreferenceController> controllerLists) {
-        controllerLists.stream()
-                .filter(controller -> controller instanceof TelephonyAvailabilityHandler)
-                .map(TelephonyAvailabilityHandler.class::cast)
-                .forEach(controller -> {
-                    controller.unsetAvailabilityStatus();
-                });
-    }
-}
diff --git a/src/com/android/settings/network/telephony/TelephonyStatusControlSession.kt b/src/com/android/settings/network/telephony/TelephonyStatusControlSession.kt
new file mode 100644
index 0000000..0e63c8c
--- /dev/null
+++ b/src/com/android/settings/network/telephony/TelephonyStatusControlSession.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.network.telephony
+
+import android.util.Log
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.coroutineScope
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.core.AbstractPreferenceController
+import com.google.common.collect.Sets
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+
+/**
+ * Session for controlling the status of TelephonyPreferenceController(s).
+ *
+ * Within this session, result of [BasePreferenceController.getAvailabilityStatus]
+ * would be under control.
+ */
+class TelephonyStatusControlSession(
+    private val controllers: Collection<AbstractPreferenceController>,
+    lifecycle: Lifecycle,
+) : AutoCloseable {
+    private var job: Job? = null
+    private val controllerSet = Sets.newConcurrentHashSet<TelephonyAvailabilityHandler>()
+
+    init {
+        job = lifecycle.coroutineScope.launch(Dispatchers.Default) {
+            for (controller in controllers) {
+                launch {
+                    setupAvailabilityStatus(controller)
+                }
+            }
+        }
+    }
+
+    /**
+     * Close the session.
+     *
+     * No longer control the status.
+     */
+    override fun close() {
+        job?.cancel()
+        unsetAvailabilityStatus()
+    }
+
+    private suspend fun setupAvailabilityStatus(controller: AbstractPreferenceController): Boolean =
+        try {
+            if (controller is TelephonyAvailabilityHandler) {
+                val status = (controller as BasePreferenceController).availabilityStatus
+                yield() // prompt cancellation guarantee
+                if (controllerSet.add(controller)) {
+                    controller.setAvailabilityStatus(status)
+                }
+            }
+            true
+        } catch (exception: Exception) {
+            Log.e(LOG_TAG, "Setup availability status failed!", exception)
+            false
+        }
+
+    private fun unsetAvailabilityStatus() {
+        for (controller in controllerSet) {
+            controller.unsetAvailabilityStatus()
+        }
+    }
+
+    companion object {
+        private const val LOG_TAG = "TelephonyStatusControlSS"
+    }
+}
diff --git a/src/com/android/settings/notification/SeekBarVolumizerFactory.java b/src/com/android/settings/notification/SeekBarVolumizerFactory.java
new file mode 100644
index 0000000..6fac2c1
--- /dev/null
+++ b/src/com/android/settings/notification/SeekBarVolumizerFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.content.Context;
+import android.net.Uri;
+import android.preference.SeekBarVolumizer;
+
+/**
+ * Testable wrapper around {@link SeekBarVolumizer} constructor.
+ */
+public class SeekBarVolumizerFactory {
+    private final Context mContext;
+
+    public SeekBarVolumizerFactory(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Creates a new SeekBarVolumizer.
+     *
+     * @param streamType of the audio manager.
+     * @param defaultUri of the volume.
+     * @param sbvc callback of the seekbar volumizer.
+     * @return a SeekBarVolumizer.
+     */
+    public SeekBarVolumizer create(int streamType, Uri defaultUri, SeekBarVolumizer.Callback sbvc) {
+        return new SeekBarVolumizer(mContext, streamType, defaultUri, sbvc);
+    }
+}
diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java
index 0000eba..9f14b73 100644
--- a/src/com/android/settings/notification/VolumeSeekBarPreference.java
+++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java
@@ -37,6 +37,8 @@
 import com.android.settings.R;
 import com.android.settings.widget.SeekBarPreference;
 
+import java.text.NumberFormat;
+import java.util.Locale;
 import java.util.Objects;
 
 /** A slider preference that directly controls an audio stream volume (no dialog) **/
@@ -47,8 +49,9 @@
 
     protected SeekBar mSeekBar;
     private int mStream;
+    private SeekBarVolumizer mVolumizer;
     @VisibleForTesting
-    SeekBarVolumizer mVolumizer;
+    SeekBarVolumizerFactory mSeekBarVolumizerFactory;
     private Callback mCallback;
     private Listener mListener;
     private ImageView mIconView;
@@ -62,30 +65,36 @@
     private boolean mStopped;
     @VisibleForTesting
     AudioManager mAudioManager;
+    private Locale mLocale;
+    private NumberFormat mNumberFormat;
 
     public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setLayoutResource(R.layout.preference_volume_slider);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
     }
 
     public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setLayoutResource(R.layout.preference_volume_slider);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
     }
 
     public VolumeSeekBarPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
         setLayoutResource(R.layout.preference_volume_slider);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
     }
 
     public VolumeSeekBarPreference(Context context) {
         super(context);
         setLayoutResource(R.layout.preference_volume_slider);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mSeekBarVolumizerFactory = new SeekBarVolumizerFactory(context);
     }
 
     public void setStream(int stream) {
@@ -143,6 +152,7 @@
                 if (mCallback != null) {
                     mCallback.onStreamValueChanged(mStream, progress);
                 }
+                overrideSeekBarStateDescription(formatStateDescription(progress));
             }
             @Override
             public void onMuted(boolean muted, boolean zenMuted) {
@@ -170,7 +180,7 @@
         };
         final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
         if (mVolumizer == null) {
-            mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);
+            mVolumizer = mSeekBarVolumizerFactory.create(mStream, sampleUri, sbvc);
         }
         mVolumizer.start();
         mVolumizer.setSeekBar(mSeekBar);
@@ -216,6 +226,33 @@
                 + "/" + R.raw.media_volume);
     }
 
+    @VisibleForTesting
+    CharSequence formatStateDescription(int progress) {
+        // This code follows the same approach in ProgressBar.java, but it rounds down the percent
+        // to match it with what the talkback feature says after any progress change. (b/285458191)
+        // Cache the locale-appropriate NumberFormat.  Configuration locale is guaranteed
+        // non-null, so the first time this is called we will always get the appropriate
+        // NumberFormat, then never regenerate it unless the locale changes on the fly.
+        Locale curLocale = getContext().getResources().getConfiguration().getLocales().get(0);
+        if (mLocale == null || !mLocale.equals(curLocale)) {
+            mLocale = curLocale;
+            mNumberFormat = NumberFormat.getPercentInstance(mLocale);
+        }
+        return mNumberFormat.format(getPercent(progress));
+    }
+
+    @VisibleForTesting
+    double getPercent(float progress) {
+        final float maxProgress = getMax();
+        final float minProgress = getMin();
+        final float diffProgress = maxProgress - minProgress;
+        if (diffProgress <= 0.0f) {
+            return 0.0f;
+        }
+        final float percent = (progress - minProgress) / diffProgress;
+        return Math.floor(Math.max(0.0f, Math.min(1.0f, percent)) * 100) / 100;
+    }
+
     public void setSuppressionText(String text) {
         if (Objects.equals(text, mSuppressionText)) return;
         mSuppressionText = text;
diff --git a/src/com/android/settings/notification/app/ConversationListPreferenceController.java b/src/com/android/settings/notification/app/ConversationListPreferenceController.java
index f893df3..6703e4e 100644
--- a/src/com/android/settings/notification/app/ConversationListPreferenceController.java
+++ b/src/com/android/settings/notification/app/ConversationListPreferenceController.java
@@ -23,6 +23,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
+import android.text.BidiFormatter;
 import android.text.TextUtils;
 
 import androidx.annotation.VisibleForTesting;
@@ -132,7 +133,7 @@
     CharSequence getTitle(ConversationChannelWrapper conversation) {
         ShortcutInfo si = conversation.getShortcutInfo();
         return si != null
-                ? si.getLabel()
+                ? BidiFormatter.getInstance().unicodeWrap(si.getLabel())
                 : conversation.getNotificationChannel().getName();
     }
 
diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java
index b71d295..4808773 100644
--- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java
+++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java
@@ -50,6 +50,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.core.graphics.ColorUtils;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -176,7 +177,8 @@
                     com.android.internal.R.id.expand_button);
             int textColor = obtainThemeColor(android.R.attr.textColorPrimary);
             int backgroundColor = obtainThemeColor(android.R.attr.colorBackgroundFloating);
-            expand.setDefaultPillColor(backgroundColor);
+            int pillColor = ColorUtils.blendARGB(textColor, backgroundColor, 0.9f);
+            expand.setDefaultPillColor(pillColor);
             expand.setDefaultTextColor(textColor);
             expand.setExpanded(false);
             header.setStateDescription(container.getVisibility() == View.VISIBLE
diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java
index c536a38..97fc343 100644
--- a/src/com/android/settings/overlay/FeatureFactory.java
+++ b/src/com/android/settings/overlay/FeatureFactory.java
@@ -31,6 +31,7 @@
 import com.android.settings.biometrics.face.FaceFeatureProvider;
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
 import com.android.settings.bluetooth.BluetoothFeatureProvider;
+import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
 import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider;
@@ -40,6 +41,7 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.gestures.AssistGestureFeatureProvider;
 import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
+import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.panel.PanelFeatureProvider;
 import com.android.settings.search.SearchFeatureProvider;
@@ -130,8 +132,7 @@
     /**
      * Gets implementation for Battery Settings provider.
      */
-    public abstract BatterySettingsFeatureProvider getBatterySettingsFeatureProvider(
-            Context context);
+    public abstract BatterySettingsFeatureProvider getBatterySettingsFeatureProvider();
 
     public abstract DashboardFeatureProvider getDashboardFeatureProvider(Context context);
 
@@ -204,6 +205,16 @@
      */
     public abstract WifiFeatureProvider getWifiFeatureProvider();
 
+    /**
+     * Retrieves implementation for keyboard settings feature.
+     */
+    public abstract KeyboardSettingsFeatureProvider getKeyboardSettingsFeatureProvider();
+
+    /**
+     * Retrieves implementation for stylus settings feature.
+     */
+    public abstract StylusFeatureProvider getStylusFeatureProvider();
+
     public static final class FactoryNotFoundException extends RuntimeException {
         public FactoryNotFoundException(Throwable throwable) {
             super("Unable to create factory. Did you misconfigure Proguard?", throwable);
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index 3ddda47..8c92792 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -42,6 +42,8 @@
 import com.android.settings.bluetooth.BluetoothFeatureProvider;
 import com.android.settings.bluetooth.BluetoothFeatureProviderImpl;
 import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl;
+import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
+import com.android.settings.connecteddevice.stylus.StylusFeatureProviderImpl;
 import com.android.settings.core.instrumentation.SettingsMetricsFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProviderImpl;
@@ -61,6 +63,8 @@
 import com.android.settings.gestures.AssistGestureFeatureProviderImpl;
 import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
 import com.android.settings.homepage.contextualcards.ContextualCardFeatureProviderImpl;
+import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider;
+import com.android.settings.inputmethod.KeyboardSettingsFeatureProviderImpl;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProviderImpl;
 import com.android.settings.panel.PanelFeatureProvider;
@@ -116,6 +120,8 @@
     private AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
     private AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
     private WifiFeatureProvider mWifiFeatureProvider;
+    private KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
+    private StylusFeatureProvider mStylusFeatureProvider;
 
     @Override
     public HardwareInfoFeatureProvider getHardwareInfoFeatureProvider() {
@@ -154,9 +160,9 @@
     }
 
     @Override
-    public BatterySettingsFeatureProvider getBatterySettingsFeatureProvider(Context context) {
+    public BatterySettingsFeatureProvider getBatterySettingsFeatureProvider() {
         if (mBatterySettingsFeatureProvider == null) {
-            mBatterySettingsFeatureProvider = new BatterySettingsFeatureProviderImpl(context);
+            mBatterySettingsFeatureProvider = new BatterySettingsFeatureProviderImpl();
         }
         return mBatterySettingsFeatureProvider;
     }
@@ -372,4 +378,20 @@
         }
         return mWifiFeatureProvider;
     }
+
+    @Override
+    public KeyboardSettingsFeatureProvider getKeyboardSettingsFeatureProvider() {
+        if (mKeyboardSettingsFeatureProvider == null) {
+            mKeyboardSettingsFeatureProvider = new KeyboardSettingsFeatureProviderImpl();
+        }
+        return mKeyboardSettingsFeatureProvider;
+    }
+
+    @Override
+    public StylusFeatureProvider getStylusFeatureProvider() {
+        if (mStylusFeatureProvider == null) {
+            mStylusFeatureProvider = new StylusFeatureProviderImpl();
+        }
+        return mStylusFeatureProvider;
+    }
 }
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index 4c4795c..0bf1255 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -33,6 +33,7 @@
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY;
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN;
 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW;
 
 import android.app.Activity;
 import android.app.Dialog;
@@ -795,6 +796,9 @@
                 if (getIntent().getBooleanExtra(EXTRA_SHOW_OPTIONS_BUTTON, false)) {
                     intent.putExtra(EXTRA_SHOW_OPTIONS_BUTTON, chooseLockSkipped);
                 }
+                if (getIntent().getBooleanExtra(EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false)) {
+                    intent.putExtra(EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, true);
+                }
                 intent.putExtra(EXTRA_CHOOSE_LOCK_GENERIC_EXTRAS, getIntent().getExtras());
                 // If the caller requested Gatekeeper Password Handle to be returned, we assume it
                 // came from biometric enrollment. onActivityResult will put the LockSettingsService
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index a72bff4..09d1924 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -65,7 +65,6 @@
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.Log;
-import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -74,9 +73,11 @@
 import android.view.inputmethod.EditorInfo;
 import android.widget.CheckBox;
 import android.widget.ImeAwareEditText;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -87,7 +88,6 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.PasswordValidationError;
 import com.android.internal.widget.TextViewInputDisabler;
-import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.SetupWizardUtils;
@@ -234,6 +234,7 @@
         private LockscreenCredential mCurrentCredential;
         private LockscreenCredential mChosenPassword;
         private boolean mRequestGatekeeperPassword;
+        private boolean mRequestWriteRepairModePassword;
         private ImeAwareEditText mPasswordEntry;
         private TextViewInputDisabler mPasswordEntryInputDisabler;
 
@@ -517,10 +518,11 @@
                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mPasswordType
                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mPasswordType;
 
-            setupPasswordRequirementsView(view);
+            final LinearLayout headerLayout = view.findViewById(
+                    R.id.sud_layout_header);
+            setupPasswordRequirementsView(headerLayout);
 
             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
-            mPasswordRestrictionView.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
             mPasswordEntry = view.findViewById(R.id.password_entry);
             mPasswordEntry.setOnEditorActionListener(this);
             mPasswordEntry.addTextChangedListener(this);
@@ -562,6 +564,8 @@
                     ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             mRequestGatekeeperPassword = intent.getBooleanExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
+            mRequestWriteRepairModePassword = intent.getBooleanExtra(
+                    ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
             if (savedInstanceState == null) {
                 updateStage(Stage.Introduction);
                 if (confirmCredentials) {
@@ -571,6 +575,7 @@
                             .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
                             .setReturnCredentials(true)
                             .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
+                            .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword)
                             .setUserId(mUserId)
                             .show();
                 }
@@ -627,11 +632,33 @@
             }
         }
 
-        private void setupPasswordRequirementsView(View view) {
-            mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view);
+        private void setupPasswordRequirementsView(@Nullable ViewGroup view) {
+            if (view == null) {
+                return;
+            }
+
+            createHintMessageView(view);
             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
-            mPasswordRequirementAdapter = new PasswordRequirementAdapter();
+            mPasswordRequirementAdapter = new PasswordRequirementAdapter(getActivity());
             mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
+            view.addView(mPasswordRestrictionView);
+        }
+
+        private void createHintMessageView(ViewGroup view) {
+            if (mPasswordRestrictionView != null) {
+                return;
+            }
+
+            final TextView sucTitleView = view.findViewById(R.id.suc_layout_title);
+            final ViewGroup.MarginLayoutParams titleLayoutParams =
+                    (ViewGroup.MarginLayoutParams) sucTitleView.getLayoutParams();
+            mPasswordRestrictionView = new RecyclerView(getActivity());
+            final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.MATCH_PARENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT);
+            lp.setMargins(titleLayoutParams.leftMargin, getResources().getDimensionPixelSize(
+                    R.dimen.password_requirement_view_margin_top), titleLayoutParams.leftMargin, 0);
+            mPasswordRestrictionView.setLayoutParams(lp);
         }
 
         @Override
@@ -1010,7 +1037,10 @@
             setNextEnabled(false);
 
             mSaveAndFinishWorker = new SaveAndFinishWorker();
-            mSaveAndFinishWorker.setListener(this);
+            mSaveAndFinishWorker
+                    .setListener(this)
+                    .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
+                    .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword);
 
             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
@@ -1030,7 +1060,7 @@
                     (mAutoPinConfirmOption != null && mAutoPinConfirmOption.isChecked()),
                     mUserId);
 
-            mSaveAndFinishWorker.start(mLockPatternUtils, mRequestGatekeeperPassword,
+            mSaveAndFinishWorker.start(mLockPatternUtils,
                     mChosenPassword, mCurrentCredential, mUserId);
         }
 
@@ -1083,50 +1113,4 @@
             }
         }
     }
-
-    public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
-
-        private LockscreenCredential mChosenPassword;
-        private LockscreenCredential mCurrentCredential;
-
-        public void start(LockPatternUtils utils, boolean requestGatekeeperPassword,
-                LockscreenCredential chosenPassword, LockscreenCredential currentCredential,
-                int userId) {
-            prepare(utils, requestGatekeeperPassword, userId);
-
-            mChosenPassword = chosenPassword;
-            mCurrentCredential = currentCredential != null ? currentCredential
-                    : LockscreenCredential.createNone();
-            mUserId = userId;
-
-            start();
-        }
-
-        @Override
-        protected Pair<Boolean, Intent> saveAndVerifyInBackground() {
-            final boolean success = mUtils.setLockCredential(
-                    mChosenPassword, mCurrentCredential, mUserId);
-            if (success) {
-                unifyProfileCredentialIfRequested();
-            }
-            Intent result = null;
-            if (success && mRequestGatekeeperPassword) {
-                // If a Gatekeeper Password was requested, invoke the LockSettingsService code
-                // path to return a Gatekeeper Password based on the credential that the user
-                // chose. This should only be run if the credential was successfully set.
-                final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenPassword,
-                        mUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
-
-                if (!response.isMatched() || !response.containsGatekeeperPasswordHandle()) {
-                    Log.e(TAG, "critical: bad response or missing GK PW handle for known good"
-                            + " password: " + response.toString());
-                }
-
-                result = new Intent();
-                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
-                        response.getGatekeeperPasswordHandle());
-            }
-            return Pair.create(success, result);
-        }
-    }
 }
diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java
index a5d04cc..7569c15 100644
--- a/src/com/android/settings/password/ChooseLockPattern.java
+++ b/src/com/android/settings/password/ChooseLockPattern.java
@@ -34,7 +34,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
-import android.util.Pair;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -53,7 +52,6 @@
 import com.android.internal.widget.LockPatternView.Cell;
 import com.android.internal.widget.LockPatternView.DisplayMode;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.SetupWizardUtils;
@@ -206,6 +204,7 @@
 
         private LockscreenCredential mCurrentCredential;
         private boolean mRequestGatekeeperPassword;
+        private boolean mRequestWriteRepairModePassword;
         protected TextView mHeaderText;
         protected LockPatternView mLockPatternView;
         protected TextView mFooterText;
@@ -563,6 +562,8 @@
                     intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
             mRequestGatekeeperPassword = intent.getBooleanExtra(
                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
+            mRequestWriteRepairModePassword = intent.getBooleanExtra(
+                    ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
 
             if (savedInstanceState == null) {
                 if (confirmCredentials) {
@@ -576,6 +577,7 @@
                             .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
                             .setReturnCredentials(true)
                             .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
+                            .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword)
                             .setUserId(mUserId)
                             .show();
 
@@ -827,7 +829,10 @@
             setRightButtonEnabled(false);
 
             mSaveAndFinishWorker = new SaveAndFinishWorker();
-            mSaveAndFinishWorker.setListener(this);
+            mSaveAndFinishWorker
+                    .setListener(this)
+                    .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
+                    .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword);
 
             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
@@ -843,7 +848,7 @@
                             profileCredential);
                 }
             }
-            mSaveAndFinishWorker.start(mLockPatternUtils, mRequestGatekeeperPassword,
+            mSaveAndFinishWorker.start(mLockPatternUtils,
                     mChosenPattern, mCurrentCredential, mUserId);
         }
 
@@ -867,51 +872,4 @@
             getActivity().finish();
         }
     }
-
-    public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
-
-        private LockscreenCredential mChosenPattern;
-        private LockscreenCredential mCurrentCredential;
-
-        public void start(LockPatternUtils utils, boolean requestGatekeeperPassword,
-                LockscreenCredential chosenPattern, LockscreenCredential currentCredential,
-                int userId) {
-            prepare(utils, requestGatekeeperPassword, userId);
-
-            mCurrentCredential = currentCredential != null ? currentCredential
-                    : LockscreenCredential.createNone();
-            mChosenPattern = chosenPattern;
-            mUserId = userId;
-
-            start();
-        }
-
-        @Override
-        protected Pair<Boolean, Intent> saveAndVerifyInBackground() {
-            final int userId = mUserId;
-            final boolean success = mUtils.setLockCredential(mChosenPattern, mCurrentCredential,
-                    userId);
-            if (success) {
-                unifyProfileCredentialIfRequested();
-            }
-            Intent result = null;
-            if (success && mRequestGatekeeperPassword) {
-                // If a Gatekeeper Password was requested, invoke the LockSettingsService code
-                // path to return a Gatekeeper Password based on the credential that the user
-                // chose. This should only be run if the credential was successfully set.
-                final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenPattern,
-                        userId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
-
-                if (!response.isMatched() || !response.containsGatekeeperPasswordHandle()) {
-                    Log.e(TAG, "critical: bad response or missing GK PW handle for known good"
-                            + " pattern: " + response.toString());
-                }
-
-                result = new Intent();
-                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
-                        response.getGatekeeperPasswordHandle());
-            }
-            return Pair.create(success, result);
-        }
-    }
 }
diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java
index 216f7db..e5fc550 100644
--- a/src/com/android/settings/password/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java
@@ -71,6 +71,10 @@
     // Gatekeeper password handle, which can subsequently be used to generate Gatekeeper
     // HardwareAuthToken(s) via LockSettingsService#verifyGatekeeperPasswordHandle
     public static final String EXTRA_KEY_GK_PW_HANDLE = "gk_pw_handle";
+    public static final String EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW =
+            "request_write_repair_mode_pw";
+    public static final String EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL =
+            "wrote_repair_mode_credential";
 
     /**
      * When EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL and EXTRA_KEY_UNIFICATION_PROFILE_ID are
@@ -152,6 +156,7 @@
         @Nullable private RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
         @Nullable private ComponentName mRemoteLockscreenValidationServiceComponent;
         private boolean mRequestGatekeeperPasswordHandle;
+        private boolean mRequestWriteRepairModePassword;
         private boolean mTaskOverlay;
 
         public Builder(@NonNull Activity activity) {
@@ -336,6 +341,17 @@
         }
 
         /**
+         * @param requestWriteRepairModePassword Set {@code true} to request that
+         * LockSettingsService writes the password data to the repair mode file after the user
+         * credential is verified successfully.
+         */
+        @NonNull public Builder setRequestWriteRepairModePassword(
+                boolean requestWriteRepairModePassword) {
+            mRequestWriteRepairModePassword = requestWriteRepairModePassword;
+            return this;
+        }
+
+        /**
          * Support of ActivityResultLauncher.
          *
          * Which allowing the launch operation be controlled externally.
@@ -348,7 +364,8 @@
         }
 
         @NonNull public ChooseLockSettingsHelper build() {
-            if (!mAllowAnyUserId && mUserId != LockPatternUtils.USER_FRP) {
+            if (!mAllowAnyUserId && mUserId != LockPatternUtils.USER_FRP
+                    && mUserId != LockPatternUtils.USER_REPAIR_MODE) {
                 Utils.enforceSameOwner(mActivity, mUserId);
             }
 
@@ -385,7 +402,7 @@
                 mBuilder.mRemoteLockscreenValidationSession,
                 mBuilder.mRemoteLockscreenValidationServiceComponent, mBuilder.mAllowAnyUserId,
                 mBuilder.mForegroundOnly, mBuilder.mRequestGatekeeperPasswordHandle,
-                mBuilder.mTaskOverlay);
+                mBuilder.mRequestWriteRepairModePassword, mBuilder.mTaskOverlay);
     }
 
     private boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@@ -396,7 +413,7 @@
             @Nullable RemoteLockscreenValidationSession remoteLockscreenValidationSession,
             @Nullable ComponentName remoteLockscreenValidationServiceComponent,
             boolean allowAnyUser, boolean foregroundOnly, boolean requestGatekeeperPasswordHandle,
-            boolean taskOverlay) {
+            boolean requestWriteRepairModePassword, boolean taskOverlay) {
         Optional<Class<?>> activityClass = determineAppropriateActivityClass(
                 returnCredentials, forceVerifyPath, userId, remoteLockscreenValidationSession);
         if (activityClass.isEmpty()) {
@@ -407,7 +424,7 @@
                 returnCredentials, external, forceVerifyPath, userId, alternateButton,
                 checkboxLabel, remoteLockscreenValidation, remoteLockscreenValidationSession,
                 remoteLockscreenValidationServiceComponent, allowAnyUser, foregroundOnly,
-                requestGatekeeperPasswordHandle, taskOverlay);
+                requestGatekeeperPasswordHandle, requestWriteRepairModePassword, taskOverlay);
     }
 
     private boolean launchConfirmationActivity(int request, CharSequence title, CharSequence header,
@@ -418,7 +435,7 @@
             @Nullable RemoteLockscreenValidationSession remoteLockscreenValidationSession,
             @Nullable ComponentName remoteLockscreenValidationServiceComponent,
             boolean allowAnyUser, boolean foregroundOnly, boolean requestGatekeeperPasswordHandle,
-            boolean taskOverlay) {
+            boolean requestWriteRepairModePassword, boolean taskOverlay) {
         final Intent intent = new Intent();
         intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title);
         intent.putExtra(ConfirmDeviceCredentialBaseFragment.HEADER_TEXT, header);
@@ -442,6 +459,8 @@
         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, allowAnyUser);
         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE,
                 requestGatekeeperPasswordHandle);
+        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW,
+                requestWriteRepairModePassword);
 
         intent.setClassName(SETTINGS_PACKAGE_NAME, activityClass.getName());
         intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index fabca6b..e4ebad7 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -20,9 +20,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PASSWORD;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
 import android.app.Activity;
 import android.app.KeyguardManager;
@@ -32,6 +30,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
@@ -166,11 +165,18 @@
         mDetails = intent.getCharSequenceExtra(KeyguardManager.EXTRA_DESCRIPTION);
         String alternateButton = intent.getStringExtra(
                 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
-        boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
-        boolean remoteValidation =
+        final boolean frp =
+                KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
+        final boolean repairMode =
+                KeyguardManager.ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL
+                        .equals(intent.getAction());
+        final boolean remoteValidation =
                 KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL.equals(intent.getAction());
         mTaskOverlay = isInternalActivity()
                 && intent.getBooleanExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, false);
+        final boolean prepareRepairMode =
+                KeyguardManager.ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL.equals(
+                        intent.getAction());
 
         mUserId = UserHandle.myUserId();
         if (isInternalActivity()) {
@@ -202,7 +208,7 @@
         }
         if (mDetails == null) {
             promptInfo.setDeviceCredentialSubtitle(
-                    getDetailsFromCredentialType(credentialType, isEffectiveUserManagedProfile));
+                    Utils.getConfirmCredentialStringForUser(this, mUserId, credentialType));
         }
 
         boolean launchedBiometric = false;
@@ -219,6 +225,14 @@
                     .setExternal(true)
                     .setUserId(LockPatternUtils.USER_FRP)
                     .show();
+        } else if (repairMode) {
+            final ChooseLockSettingsHelper.Builder builder =
+                    new ChooseLockSettingsHelper.Builder(this);
+            launchedCDC = builder.setHeader(mTitle)
+                    .setDescription(mDetails)
+                    .setExternal(true)
+                    .setUserId(LockPatternUtils.USER_REPAIR_MODE)
+                    .show();
         } else if (remoteValidation) {
             RemoteLockscreenValidationSession remoteLockscreenValidationSession =
                     intent.getParcelableExtra(
@@ -244,6 +258,17 @@
                     .setExternal(true)
                     .show();
             return;
+        } else if (prepareRepairMode) {
+            final ChooseLockSettingsHelper.Builder builder =
+                    new ChooseLockSettingsHelper.Builder(this);
+            launchedCDC = builder.setHeader(mTitle)
+                    .setDescription(mDetails)
+                    .setExternal(true)
+                    .setUserId(mUserId)
+                    .setTaskOverlay(mTaskOverlay)
+                    .setRequestWriteRepairModePassword(true)
+                    .setForceVerifyPath(true)
+                    .show();
         } else if (isEffectiveUserManagedProfile && isInternalActivity()) {
             mCredentialMode = CREDENTIAL_MANAGED;
             if (isBiometricAllowed(effectiveUserId, mUserId)) {
@@ -314,45 +339,18 @@
         return null;
     }
 
-    private String getDetailsFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
-            boolean isEffectiveUserManagedProfile) {
-        switch (credentialType) {
-            case LockPatternUtils.CREDENTIAL_TYPE_PIN:
-                if (isEffectiveUserManagedProfile) {
-                    return mDevicePolicyManager.getResources().getString(WORK_PROFILE_CONFIRM_PIN,
-                            () -> getString(
-                                    R.string.lockpassword_confirm_your_pin_generic_profile));
-                }
-
-                return getString(R.string.lockpassword_confirm_your_pin_generic);
-            case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
-                if (isEffectiveUserManagedProfile) {
-                    return mDevicePolicyManager.getResources().getString(
-                            WORK_PROFILE_CONFIRM_PATTERN,
-                            () -> getString(
-                                    R.string.lockpassword_confirm_your_pattern_generic_profile));
-                }
-
-                return getString(R.string.lockpassword_confirm_your_pattern_generic);
-            case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
-                if (isEffectiveUserManagedProfile) {
-                    return mDevicePolicyManager.getResources().getString(
-                            WORK_PROFILE_CONFIRM_PASSWORD,
-                            () -> getString(
-                                    R.string.lockpassword_confirm_your_password_generic_profile));
-                }
-
-                return getString(R.string.lockpassword_confirm_your_password_generic);
-        }
-        return null;
-    }
-
     @Override
     protected void onStart() {
         super.onStart();
         // Translucent activity that is "visible", so it doesn't complain about finish()
         // not being called before onResume().
         setVisible(true);
+
+        if ((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                != Configuration.UI_MODE_NIGHT_YES) {
+            getWindow().getInsetsController().setSystemBarsAppearance(
+                    APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index f4cfabc..43d8440 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -105,6 +105,8 @@
     protected final Handler mHandler = new Handler();
     protected boolean mFrp;
     protected boolean mRemoteValidation;
+    protected boolean mRequestWriteRepairModePassword;
+    protected boolean mRepairMode;
     protected CharSequence mAlternateButtonText;
     protected BiometricManager mBiometricManager;
     @Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
@@ -130,6 +132,8 @@
                 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
         mForceVerifyPath = intent.getBooleanExtra(
                 ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false);
+        mRequestWriteRepairModePassword = intent.getBooleanExtra(
+                ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
 
         if (intent.getBooleanExtra(IS_REMOTE_LOCKSCREEN_VALIDATION, false)) {
             if (FeatureFlagUtils.isEnabled(getContext(),
@@ -178,6 +182,7 @@
         mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(),
                 isInternalActivity());
         mFrp = (mUserId == LockPatternUtils.USER_FRP);
+        mRepairMode = (mUserId == LockPatternUtils.USER_REPAIR_MODE);
         mUserManager = UserManager.get(getActivity());
         mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
         mLockPatternUtils = new LockPatternUtils(getActivity());
@@ -266,7 +271,7 @@
     // verifyTiedProfileChallenge. In such case, we also wanna show the user message that
     // fingerprint is disabled due to device restart.
     protected boolean isStrongAuthRequired() {
-        return mFrp
+        return mFrp || mRepairMode
                 || !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
                 || !mUserManager.isUserUnlocked(mUserId);
     }
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index 03b89f2..b203015 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -18,12 +18,8 @@
 
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PASSWORD;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED;
-import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PIN_REQUIRED;
 import static android.app.admin.DevicePolicyResources.UNDEFINED;
 
 import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle;
@@ -75,27 +71,12 @@
 
 public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
 
-    // The index of the array is isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
+    // The index of the array is isStrongAuth << 1 + isAlpha.
     private static final int[] DETAIL_TEXTS = new int[] {
         R.string.lockpassword_confirm_your_pin_generic,
         R.string.lockpassword_confirm_your_password_generic,
-        R.string.lockpassword_confirm_your_pin_generic_profile,
-        R.string.lockpassword_confirm_your_password_generic_profile,
         R.string.lockpassword_strong_auth_required_device_pin,
         R.string.lockpassword_strong_auth_required_device_password,
-        R.string.lockpassword_strong_auth_required_work_pin,
-        R.string.lockpassword_strong_auth_required_work_password
-    };
-
-    private static final String[] DETAIL_TEXT_OVERRIDES = new String[] {
-            UNDEFINED,
-            UNDEFINED,
-            WORK_PROFILE_CONFIRM_PIN,
-            WORK_PROFILE_CONFIRM_PASSWORD,
-            UNDEFINED,
-            UNDEFINED,
-            WORK_PROFILE_PIN_REQUIRED,
-            WORK_PROFILE_PASSWORD_REQUIRED
     };
 
     public static class InternalActivity extends ConfirmLockPassword {
@@ -125,7 +106,7 @@
 
     public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
             implements OnClickListener, OnEditorActionListener,
-            CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener,
+            CredentialCheckResultTracker.Listener, SaveAndFinishWorker.Listener,
             RemoteLockscreenValidationFragment.Listener {
         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
         private ImeAwareEditText mPasswordEntry;
@@ -200,7 +181,12 @@
                     detailsMessage = getDefaultDetails();
                 }
                 mGlifLayout.setHeaderText(headerMessage);
-                mGlifLayout.setDescriptionText(detailsMessage);
+
+                if (mIsManagedProfile) {
+                    mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
+                } else {
+                    mGlifLayout.setDescriptionText(detailsMessage);
+                }
                 mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
             }
             int currentType = mPasswordEntry.getInputType();
@@ -284,6 +270,11 @@
                 return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_header_frp)
                         : getString(R.string.lockpassword_confirm_your_pin_header_frp);
             }
+            if (mRepairMode) {
+                return mIsAlpha
+                        ? getString(R.string.lockpassword_confirm_repair_mode_password_header)
+                        : getString(R.string.lockpassword_confirm_repair_mode_pin_header);
+            }
             if (mRemoteValidation) {
                 return getString(R.string.lockpassword_remote_validation_header);
             }
@@ -307,17 +298,20 @@
                 return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_details_frp)
                         : getString(R.string.lockpassword_confirm_your_pin_details_frp);
             }
+            if (mRepairMode) {
+                return mIsAlpha
+                        ? getString(R.string.lockpassword_confirm_repair_mode_password_details)
+                        : getString(R.string.lockpassword_confirm_repair_mode_pin_details);
+            }
             if (mRemoteValidation) {
                 return getContext().getString(mIsAlpha
                         ? R.string.lockpassword_remote_validation_password_details
                         : R.string.lockpassword_remote_validation_pin_details);
             }
             boolean isStrongAuthRequired = isStrongAuthRequired();
-            // Map boolean flags to an index by isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
-            int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((mIsManagedProfile ? 1 : 0) << 1)
-                    + (mIsAlpha ? 1 : 0);
-            return mDevicePolicyManager.getResources().getString(
-                    DETAIL_TEXT_OVERRIDES[index], () -> getString(DETAIL_TEXTS[index]));
+            // Map boolean flags to an index by isStrongAuth << 1 + isAlpha.
+            int index = ((isStrongAuthRequired ? 1 : 0) << 1) + (mIsAlpha ? 1 : 0);
+            return getString(DETAIL_TEXTS[index]);
         }
 
         private String getDefaultCheckboxLabel() {
@@ -496,7 +490,9 @@
                 }
             } else if (mForceVerifyPath)  {
                 if (isInternalActivity()) {
-                    startVerifyPassword(credential, intent, 0 /* flags */);
+                    final int flags = mRequestWriteRepairModePassword
+                            ? LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW : 0;
+                    startVerifyPassword(credential, intent, flags);
                     return;
                 }
             } else {
@@ -621,15 +617,15 @@
                     if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
                             .getLockscreenCredential() != null) {
                         Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
-                        ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker =
-                                new ChooseLockPassword.SaveAndFinishWorker();
+                        SaveAndFinishWorker saveAndFinishWorker = new SaveAndFinishWorker();
                         getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
                                 .commit();
                         getFragmentManager().executePendingTransactions();
-                        saveAndFinishWorker.setListener(this);
+                        saveAndFinishWorker
+                                .setListener(this)
+                                .setRequestGatekeeperPasswordHandle(true);
                         saveAndFinishWorker.start(
                                 mLockPatternUtils,
-                                /* requestGatekeeperPassword= */ true,
                                 mRemoteLockscreenValidationFragment.getLockscreenCredential(),
                                 /* currentCredential= */ null,
                                 mEffectiveUserId);
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index e99a986..7160d64 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -93,7 +93,7 @@
 
     public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
             implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
-            SaveChosenLockWorkerBase.Listener, RemoteLockscreenValidationFragment.Listener {
+            SaveAndFinishWorker.Listener, RemoteLockscreenValidationFragment.Listener {
 
         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
 
@@ -179,7 +179,7 @@
                 //              ability to disable the pattern in L. Remove this block after
                 //              ensuring it's safe to do so. (Note that ConfirmLockPassword
                 //              doesn't have this).
-                if (!mFrp && !mRemoteValidation
+                if (!mFrp && !mRemoteValidation && !mRepairMode
                         && !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
                     getActivity().setResult(Activity.RESULT_OK);
                     getActivity().finish();
@@ -308,17 +308,17 @@
             if (mFrp) {
                 return getString(R.string.lockpassword_confirm_your_pattern_details_frp);
             }
+            if (mRepairMode) {
+                return getString(R.string.lockpassword_confirm_repair_mode_pattern_details);
+            }
             if (mRemoteValidation) {
                 return getString(
                         R.string.lockpassword_remote_validation_pattern_details);
             }
             final boolean isStrongAuthRequired = isStrongAuthRequired();
-            if (!mIsManagedProfile) {
-                return isStrongAuthRequired
-                        ? getString(R.string.lockpassword_strong_auth_required_device_pattern)
-                        : getString(R.string.lockpassword_confirm_your_pattern_generic);
-            }
-            return null;
+            return isStrongAuthRequired
+                    ? getString(R.string.lockpassword_strong_auth_required_device_pattern)
+                    : getString(R.string.lockpassword_confirm_your_pattern_generic);
         }
 
         private Object[][] getActiveViews() {
@@ -368,7 +368,10 @@
 
                     CharSequence detailsText =
                             mDetailsText == null ? getDefaultDetails() : mDetailsText;
-                    if (detailsText != null) {
+
+                    if (mIsManagedProfile) {
+                        mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
+                    } else {
                         mGlifLayout.setDescriptionText(detailsText);
                     }
 
@@ -402,7 +405,12 @@
         }
 
         private String getDefaultHeader() {
-            if (mFrp) return getString(R.string.lockpassword_confirm_your_pattern_header_frp);
+            if (mFrp) {
+                return getString(R.string.lockpassword_confirm_your_pattern_header_frp);
+            }
+            if (mRepairMode) {
+                return getString(R.string.lockpassword_confirm_repair_mode_pattern_header);
+            }
             if (mRemoteValidation) {
                 return getString(R.string.lockpassword_remote_validation_header);
             }
@@ -512,7 +520,9 @@
                     }
                 } else if (mForceVerifyPath) {
                     if (isInternalActivity()) {
-                        startVerifyPattern(credential, intent, 0 /* flags */);
+                        final int flags = mRequestWriteRepairModePassword
+                                ? LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW : 0;
+                        startVerifyPattern(credential, intent, flags);
                         return;
                     }
                 } else {
@@ -620,15 +630,15 @@
                     if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
                             .getLockscreenCredential() != null) {
                         Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
-                        ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker =
-                                new ChooseLockPattern.SaveAndFinishWorker();
+                        SaveAndFinishWorker saveAndFinishWorker = new SaveAndFinishWorker();
                         getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
                                 .commit();
                         getFragmentManager().executePendingTransactions();
-                        saveAndFinishWorker.setListener(this);
+                        saveAndFinishWorker
+                                .setListener(this)
+                                .setRequestGatekeeperPasswordHandle(true);
                         saveAndFinishWorker.start(
                                 mLockPatternUtils,
-                                /* requestGatekeeperPassword= */ true,
                                 mRemoteLockscreenValidationFragment.getLockscreenCredential(),
                                 /* currentCredential= */ null,
                                 mEffectiveUserId);
diff --git a/src/com/android/settings/password/ForgotPasswordActivity.java b/src/com/android/settings/password/ForgotPasswordActivity.java
index 9afda18..92dc336 100644
--- a/src/com/android/settings/password/ForgotPasswordActivity.java
+++ b/src/com/android/settings/password/ForgotPasswordActivity.java
@@ -50,6 +50,7 @@
             finish();
             return;
         }
+        ThemeHelper.trySetDynamicColor(this);
         setContentView(R.layout.forgot_password_activity);
 
         DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
diff --git a/src/com/android/settings/password/PasswordRequirementAdapter.java b/src/com/android/settings/password/PasswordRequirementAdapter.java
index 0e194af..0d8f02e 100644
--- a/src/com/android/settings/password/PasswordRequirementAdapter.java
+++ b/src/com/android/settings/password/PasswordRequirementAdapter.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.password;
 
+import android.annotation.NonNull;
+import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -31,9 +33,12 @@
  */
 public class PasswordRequirementAdapter extends
         RecyclerView.Adapter<PasswordRequirementViewHolder> {
-    private String[] mRequirements;
 
-    public PasswordRequirementAdapter() {
+    private String[] mRequirements;
+    private Context mContext;
+
+    public PasswordRequirementAdapter(Context context) {
+        mContext = context;
         setHasStableIds(true);
     }
 
@@ -60,8 +65,18 @@
     }
 
     @Override
+    public void onViewAttachedToWindow(@NonNull PasswordRequirementViewHolder holder) {
+        holder.mDescriptionText.announceForAccessibility(holder.mDescriptionText.getText());
+    }
+
+    @Override
     public void onBindViewHolder(PasswordRequirementViewHolder holder, int position) {
+        final int fontSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.password_requirement_font_size);
         holder.mDescriptionText.setText(mRequirements[position]);
+        holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle);
+        holder.mDescriptionText.setTextSize(fontSize / mContext.getResources()
+                .getDisplayMetrics().scaledDensity);
     }
 
     public static class PasswordRequirementViewHolder extends RecyclerView.ViewHolder {
diff --git a/src/com/android/settings/password/PasswordUtils.java b/src/com/android/settings/password/PasswordUtils.java
index e8e309c..a7edc89 100644
--- a/src/com/android/settings/password/PasswordUtils.java
+++ b/src/com/android/settings/password/PasswordUtils.java
@@ -27,7 +27,13 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
+import com.android.settings.R;
 import com.android.settings.Utils;
 
 public final class PasswordUtils extends com.android.settingslib.Utils {
@@ -97,4 +103,25 @@
             Log.v(TAG, "Could not talk to activity manager.", e);
         }
     }
+
+    /** Setup screen lock options button under the Glif Header. */
+    public static void setupScreenLockOptionsButton(Context context, View view, Button optButton) {
+        final LinearLayout headerLayout = view.findViewById(
+                R.id.sud_layout_header);
+        final TextView sucTitleView = headerLayout.findViewById(R.id.suc_layout_title);
+        if (headerLayout != null && sucTitleView != null) {
+            final ViewGroup.MarginLayoutParams layoutTitleParams =
+                    (ViewGroup.MarginLayoutParams) sucTitleView.getLayoutParams();
+            final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+            lp.leftMargin = layoutTitleParams.leftMargin;
+            lp.topMargin = (int) context.getResources().getDimensionPixelSize(
+                    R.dimen.screen_lock_options_button_margin_top);
+            optButton.setPadding(0, 0, 0, 0);
+            optButton.setLayoutParams(lp);
+            optButton.setText(context.getString(R.string.setup_lock_settings_options_button_label));
+            headerLayout.addView(optButton);
+        }
+    }
 }
diff --git a/src/com/android/settings/password/SaveAndFinishWorker.java b/src/com/android/settings/password/SaveAndFinishWorker.java
new file mode 100644
index 0000000..40054b7
--- /dev/null
+++ b/src/com/android/settings/password/SaveAndFinishWorker.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.password;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.widget.Toast;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.fragment.app.Fragment;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.settings.R;
+import com.android.settings.safetycenter.LockScreenSafetySource;
+
+/**
+ * An invisible retained worker fragment to track the AsyncWork that saves (and optionally
+ * verifies if a challenge is given) the chosen lock credential (pattern/pin/password).
+ */
+public class SaveAndFinishWorker extends Fragment {
+    private static final String TAG = "SaveAndFinishWorker";
+
+    private Listener mListener;
+    private boolean mFinished;
+    private Intent mResultData;
+
+    private LockPatternUtils mUtils;
+    private boolean mRequestGatekeeperPassword;
+    private boolean mRequestWriteRepairModePassword;
+    private boolean mWasSecureBefore;
+    private int mUserId;
+    private int mUnificationProfileId = UserHandle.USER_NULL;
+    private LockscreenCredential mUnificationProfileCredential;
+    private LockscreenCredential mChosenCredential;
+    private LockscreenCredential mCurrentCredential;
+
+    private boolean mBlocking;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setRetainInstance(true);
+    }
+
+    public SaveAndFinishWorker setListener(Listener listener) {
+        if (mListener == listener) {
+            return this;
+        }
+
+        mListener = listener;
+        if (mFinished && mListener != null) {
+            mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
+        }
+        return this;
+    }
+
+    @VisibleForTesting
+    void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential,
+            LockscreenCredential currentCredential, int userId) {
+        mUtils = utils;
+        mUserId = userId;
+        // This will be a no-op for non managed profiles.
+        mWasSecureBefore = mUtils.isSecure(mUserId);
+        mFinished = false;
+        mResultData = null;
+
+        mChosenCredential = chosenCredential;
+        mCurrentCredential = currentCredential != null ? currentCredential
+                : LockscreenCredential.createNone();
+    }
+
+    public void start(LockPatternUtils utils, LockscreenCredential chosenCredential,
+            LockscreenCredential currentCredential, int userId) {
+        prepare(utils, chosenCredential, currentCredential, userId);
+        if (mBlocking) {
+            finish(saveAndVerifyInBackground().second);
+        } else {
+            new Task().execute();
+        }
+    }
+
+    /**
+     * Executes the save and verify work in background.
+     * @return pair where the first is a boolean confirming whether the change was successful or not
+     * and second is the Intent which has the challenge token or is null.
+     */
+    @VisibleForTesting
+    Pair<Boolean, Intent> saveAndVerifyInBackground() {
+        final int userId = mUserId;
+        try {
+            if (!mUtils.setLockCredential(mChosenCredential, mCurrentCredential, userId)) {
+                return Pair.create(false, null);
+            }
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to set lockscreen credential", e);
+            return Pair.create(false, null);
+        }
+
+        unifyProfileCredentialIfRequested();
+
+        @LockPatternUtils.VerifyFlag int flags = 0;
+        if (mRequestGatekeeperPassword) {
+            // If a Gatekeeper Password was requested, invoke the LockSettingsService code
+            // path to return a Gatekeeper Password based on the credential that the user
+            // chose. This should only be run if the credential was successfully set.
+            flags |= LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
+        }
+        if (mRequestWriteRepairModePassword) {
+            flags |= LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+        }
+        if (flags == 0) {
+            return Pair.create(true, null);
+        }
+
+        Intent result = new Intent();
+        final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenCredential,
+                userId, flags);
+        if (response.isMatched()) {
+            if (mRequestGatekeeperPassword && response.containsGatekeeperPasswordHandle()) {
+                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
+                        response.getGatekeeperPasswordHandle());
+            } else if (mRequestGatekeeperPassword) {
+                Log.e(TAG, "critical: missing GK PW handle for known good credential: " + response);
+            }
+        } else {
+            Log.e(TAG, "critical: bad response for known good credential: " + response);
+        }
+        if (mRequestWriteRepairModePassword) {
+            // Notify the caller if repair mode credential is saved successfully
+            result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL,
+                    response.isMatched());
+        }
+
+        return Pair.create(true, result);
+    }
+
+    private void finish(Intent resultData) {
+        mFinished = true;
+        mResultData = resultData;
+        if (mListener != null) {
+            mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
+        }
+        if (mUnificationProfileCredential != null) {
+            mUnificationProfileCredential.zeroize();
+        }
+        LockScreenSafetySource.onLockScreenChange(getContext());
+    }
+
+    public SaveAndFinishWorker setRequestGatekeeperPasswordHandle(boolean value) {
+        mRequestGatekeeperPassword = value;
+        return this;
+    }
+
+    public SaveAndFinishWorker setRequestWriteRepairModePassword(boolean value) {
+        mRequestWriteRepairModePassword = value;
+        return this;
+    }
+
+    public SaveAndFinishWorker setBlocking(boolean blocking) {
+        mBlocking = blocking;
+        return this;
+    }
+
+    public SaveAndFinishWorker setProfileToUnify(
+            int profileId, LockscreenCredential credential) {
+        mUnificationProfileId = profileId;
+        mUnificationProfileCredential = credential.duplicate();
+        return this;
+    }
+
+    private void unifyProfileCredentialIfRequested() {
+        if (mUnificationProfileId != UserHandle.USER_NULL) {
+            mUtils.setSeparateProfileChallengeEnabled(mUnificationProfileId, false,
+                    mUnificationProfileCredential);
+        }
+    }
+
+    private class Task extends AsyncTask<Void, Void, Pair<Boolean, Intent>> {
+
+        @Override
+        protected Pair<Boolean, Intent> doInBackground(Void... params){
+            return saveAndVerifyInBackground();
+        }
+
+        @Override
+        protected void onPostExecute(Pair<Boolean, Intent> resultData) {
+            if (!resultData.first) {
+                Toast.makeText(getContext(), R.string.lockpassword_credential_changed,
+                        Toast.LENGTH_LONG).show();
+            }
+            finish(resultData.second);
+        }
+    }
+
+    interface Listener {
+        void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData);
+    }
+}
diff --git a/src/com/android/settings/password/SaveChosenLockWorkerBase.java b/src/com/android/settings/password/SaveChosenLockWorkerBase.java
deleted file mode 100644
index 4864941..0000000
--- a/src/com/android/settings/password/SaveChosenLockWorkerBase.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.password;
-
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Pair;
-import android.widget.Toast;
-
-import androidx.fragment.app.Fragment;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.settings.R;
-import com.android.settings.safetycenter.LockScreenSafetySource;
-
-/**
- * An invisible retained worker fragment to track the AsyncWork that saves (and optionally
- * verifies if a challenge is given) the chosen lock credential (pattern/pin/password).
- */
-abstract class SaveChosenLockWorkerBase extends Fragment {
-
-    private Listener mListener;
-    private boolean mFinished;
-    private Intent mResultData;
-
-    protected LockPatternUtils mUtils;
-    protected boolean mRequestGatekeeperPassword;
-    protected boolean mWasSecureBefore;
-    protected int mUserId;
-    protected int mUnificationProfileId = UserHandle.USER_NULL;
-    protected LockscreenCredential mUnificationProfileCredential;
-
-    private boolean mBlocking;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setRetainInstance(true);
-    }
-
-    public void setListener(Listener listener) {
-        if (mListener == listener) {
-            return;
-        }
-
-        mListener = listener;
-        if (mFinished && mListener != null) {
-            mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
-        }
-    }
-
-    protected void prepare(LockPatternUtils utils, boolean requestGatekeeperPassword, int userId) {
-        mUtils = utils;
-        mUserId = userId;
-        mRequestGatekeeperPassword = requestGatekeeperPassword;
-        // This will be a no-op for non managed profiles.
-        mWasSecureBefore = mUtils.isSecure(mUserId);
-        mFinished = false;
-        mResultData = null;
-    }
-
-    protected void start() {
-        if (mBlocking) {
-            finish(saveAndVerifyInBackground().second);
-        } else {
-            new Task().execute();
-        }
-    }
-
-    /**
-     * Executes the save and verify work in background.
-     * @return pair where the first is a boolean confirming whether the change was successful or not
-     * and second is the Intent which has the challenge token or is null.
-     */
-    protected abstract Pair<Boolean, Intent> saveAndVerifyInBackground();
-
-    protected void finish(Intent resultData) {
-        mFinished = true;
-        mResultData = resultData;
-        if (mListener != null) {
-            mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
-        }
-        if (mUnificationProfileCredential != null) {
-            mUnificationProfileCredential.zeroize();
-        }
-        LockScreenSafetySource.onLockScreenChange(getContext());
-    }
-
-    public void setBlocking(boolean blocking) {
-        mBlocking = blocking;
-    }
-
-    public void setProfileToUnify(int profileId, LockscreenCredential credential) {
-        mUnificationProfileId = profileId;
-        mUnificationProfileCredential = credential.duplicate();
-    }
-
-    protected void unifyProfileCredentialIfRequested() {
-        if (mUnificationProfileId != UserHandle.USER_NULL) {
-            mUtils.setSeparateProfileChallengeEnabled(mUnificationProfileId, false,
-                    mUnificationProfileCredential);
-        }
-    }
-
-    private class Task extends AsyncTask<Void, Void, Pair<Boolean, Intent>> {
-
-        @Override
-        protected Pair<Boolean, Intent> doInBackground(Void... params){
-            return saveAndVerifyInBackground();
-        }
-
-        @Override
-        protected void onPostExecute(Pair<Boolean, Intent> resultData) {
-            if (!resultData.first) {
-                Toast.makeText(getContext(), R.string.lockpassword_credential_changed,
-                        Toast.LENGTH_LONG).show();
-            }
-            finish(resultData.second);
-        }
-    }
-
-    interface Listener {
-        void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData);
-    }
-}
diff --git a/src/com/android/settings/password/SetupChooseLockPassword.java b/src/com/android/settings/password/SetupChooseLockPassword.java
index 0101aa5..d0d7d93 100644
--- a/src/com/android/settings/password/SetupChooseLockPassword.java
+++ b/src/com/android/settings/password/SetupChooseLockPassword.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.widget.Button;
 
@@ -97,7 +98,10 @@
             }
 
             if (showOptionsButton && anyOptionsShown) {
-                mOptionsButton = view.findViewById(R.id.screen_lock_options);
+                mOptionsButton = new Button(new ContextThemeWrapper(getActivity(),
+                        R.style.SudGlifButton_Tertiary));
+                mOptionsButton.setId(R.id.screen_lock_options);
+                PasswordUtils.setupScreenLockOptionsButton(getActivity(), view, mOptionsButton);
                 mOptionsButton.setVisibility(View.VISIBLE);
                 mOptionsButton.setOnClickListener((btn) ->
                         ChooseLockTypeDialogFragment.newInstance(mUserId)
diff --git a/src/com/android/settings/password/SetupChooseLockPattern.java b/src/com/android/settings/password/SetupChooseLockPattern.java
index 2cad181..560906d 100644
--- a/src/com/android/settings/password/SetupChooseLockPattern.java
+++ b/src/com/android/settings/password/SetupChooseLockPattern.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -83,7 +84,10 @@
                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
             View view = super.onCreateView(inflater, container, savedInstanceState);
             if (!getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) {
-                mOptionsButton = view.findViewById(R.id.screen_lock_options);
+                mOptionsButton = new Button(new ContextThemeWrapper(getActivity(),
+                        R.style.SudGlifButton_Tertiary));
+                mOptionsButton.setId(R.id.screen_lock_options);
+                PasswordUtils.setupScreenLockOptionsButton(getActivity(), view, mOptionsButton);
                 mOptionsButton.setOnClickListener((btn) ->
                         ChooseLockTypeDialogFragment.newInstance(mUserId)
                                 .show(getChildFragmentManager(), TAG_SKIP_SCREEN_LOCK_DIALOG));
diff --git a/src/com/android/settings/print/PrintServiceSettingsFragment.java b/src/com/android/settings/print/PrintServiceSettingsFragment.java
index 39b5ab9..c5316e6 100644
--- a/src/com/android/settings/print/PrintServiceSettingsFragment.java
+++ b/src/com/android/settings/print/PrintServiceSettingsFragment.java
@@ -17,6 +17,7 @@
 package com.android.settings.print;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
 import android.content.Context;
@@ -547,8 +548,13 @@
                     @Override
                     public void onClick(View v) {
                         try {
+                            Bundle options = ActivityOptions.makeBasic()
+                                    .setPendingIntentBackgroundActivityStartMode(
+                                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                    .toBundle();
                             getActivity().startIntentSender(
-                                    printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
+                                    printer.getInfoIntent().getIntentSender(), null, 0, 0, 0,
+                                    options);
                         } catch (SendIntentException e) {
                             Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
                         }
diff --git a/src/com/android/settings/regionalpreferences/NumberingSystemItemController.java b/src/com/android/settings/regionalpreferences/NumberingSystemItemController.java
index e3a8d23..2a99e99 100644
--- a/src/com/android/settings/regionalpreferences/NumberingSystemItemController.java
+++ b/src/com/android/settings/regionalpreferences/NumberingSystemItemController.java
@@ -153,7 +153,7 @@
     private void handleLanguageSelect(Preference preference) {
         String selectedLanguage = preference.getKey();
         mMetricsFeatureProvider.action(mContext,
-                SettingsEnums.ACTION_CHOOSE_LANGUAGE_FOR_NUMBERS_PREFERENCES);
+                SettingsEnums.ACTION_CHOOSE_LANGUAGE_FOR_NUMBERS_PREFERENCES, selectedLanguage);
         final Bundle extra = new Bundle();
         extra.putString(RegionalPreferencesEntriesFragment.ARG_KEY_REGIONAL_PREFERENCE,
                 ARG_VALUE_NUMBERING_SYSTEM_SELECT);
@@ -177,7 +177,8 @@
                         saveNumberingSystemToLocale(Locale.forLanguageTag(mSelectedLanguage),
                                 numberingSystem);
                 mMetricsFeatureProvider.action(mContext,
-                        SettingsEnums.ACTION_SET_NUMBERS_PREFERENCES);
+                        SettingsEnums.ACTION_SET_NUMBERS_PREFERENCES,
+                        updatedLocale.getDisplayName() + ": " + numberingSystem);
                 // After updated locale to framework, this fragment will recreate,
                 // so it needs to update the argument of selected language.
                 Bundle bundle = new Bundle();
diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
index 1e39fff..432ce0e 100644
--- a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
+++ b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
@@ -59,6 +59,8 @@
             TickButtonPreference pref = new TickButtonPreference(mContext);
             mPreferenceCategory.addPreference(pref);
             final String item = unitValues[i];
+            final String value = RegionalPreferencesDataUtils.getDefaultUnicodeExtensionData(
+                    mContext, getExtensionTypes());
             pref.setTitle(getPreferenceTitle(item));
             pref.setKey(item);
             pref.setOnPreferenceClickListener(clickedPref -> {
@@ -66,11 +68,10 @@
                 RegionalPreferencesDataUtils.savePreference(mContext, getExtensionTypes(),
                         item.equals(RegionalPreferencesDataUtils.DEFAULT_VALUE)
                                 ? null : item);
-                mMetricsFeatureProvider.action(mContext, getMetricsActionKey());
+                mMetricsFeatureProvider.action(mContext, getMetricsActionKey(),
+                        getPreferenceTitle(value) + " > " +  getPreferenceTitle(item));
                 return true;
             });
-            String value = RegionalPreferencesDataUtils.getDefaultUnicodeExtensionData(mContext,
-                    getExtensionTypes());
             pref.setSelected(!value.isEmpty() && item.equals(value));
         }
     }
diff --git a/src/com/android/settings/security/ScreenPinningSettings.java b/src/com/android/settings/security/ScreenPinningSettings.java
index e219b44..8fae6e1 100644
--- a/src/com/android/settings/security/ScreenPinningSettings.java
+++ b/src/com/android/settings/security/ScreenPinningSettings.java
@@ -23,7 +23,6 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.SearchIndexableResource;
 import android.provider.Settings;
 import android.widget.Switch;
 
@@ -38,14 +37,12 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.password.ChooseLockGeneric;
+import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.widget.SettingsMainSwitchBar;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.FooterPreference;
 import com.android.settingslib.widget.OnMainSwitchChangeListener;
-
-import java.util.Arrays;
-import java.util.List;
 /**
  * Screen pinning settings.
  */
@@ -56,6 +53,7 @@
     private static final String KEY_USE_SCREEN_LOCK = "use_screen_lock";
     private static final String KEY_FOOTER = "screen_pinning_settings_screen_footer";
     private static final int CHANGE_LOCK_METHOD_REQUEST = 43;
+    private static final int CONFIRM_REQUEST = 1000;
 
     private SettingsMainSwitchBar mSwitchBar;
     private SwitchPreference mUseScreenLock;
@@ -129,10 +127,10 @@
     }
 
     private boolean setScreenLockUsed(boolean isEnabled) {
+        LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
+        final int passwordQuality = lockPatternUtils
+                .getKeyguardStoredPasswordQuality(UserHandle.myUserId());
         if (isEnabled) {
-            LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
-            int passwordQuality = lockPatternUtils
-                    .getKeyguardStoredPasswordQuality(UserHandle.myUserId());
             if (passwordQuality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                 Intent chooseLockIntent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
                 chooseLockIntent.putExtra(
@@ -141,6 +139,12 @@
                 startActivityForResult(chooseLockIntent, CHANGE_LOCK_METHOD_REQUEST);
                 return false;
             }
+        }  else {
+            if (passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                final ChooseLockSettingsHelper.Builder builder =
+                        new ChooseLockSettingsHelper.Builder(getActivity(), this);
+                return builder.setRequestCode(CONFIRM_REQUEST).show();
+            }
         }
         setScreenLockUsedSetting(isEnabled);
         return true;
@@ -162,6 +166,8 @@
             setScreenLockUsed(validPassQuality);
             // Make sure the screen updates.
             mUseScreenLock.setChecked(validPassQuality);
+        } else if (requestCode == CONFIRM_REQUEST) {
+            setScreenLockUsedSetting(false);
         }
     }
 
@@ -245,14 +251,5 @@
      * For search
      */
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider() {
-
-                @Override
-                public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
-                        boolean enabled) {
-                    final SearchIndexableResource sir = new SearchIndexableResource(context);
-                    sir.xmlResId = R.xml.screen_pinning_settings;
-                    return Arrays.asList(sir);
-                }
-            };
+            new BaseSearchIndexProvider(R.xml.screen_pinning_settings);
 }
diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java
index 7d39938..e7b0185 100644
--- a/src/com/android/settings/sim/SimDialogActivity.java
+++ b/src/com/android/settings/sim/SimDialogActivity.java
@@ -280,8 +280,20 @@
     public void showEnableAutoDataSwitchDialog() {
         final FragmentManager fragmentManager = getSupportFragmentManager();
         SimDialogFragment fragment = createFragment(ENABLE_AUTO_DATA_SWITCH);
-        fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH));
 
+        if (fragmentManager.isStateSaved()) {
+            Log.w(TAG, "Failed to show EnableAutoDataSwitchDialog. The fragmentManager "
+                    + "is StateSaved.");
+            forceClose();
+            return;
+        }
+        try {
+            fragment.show(fragmentManager, Integer.toString(ENABLE_AUTO_DATA_SWITCH));
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to show EnableAutoDataSwitchDialog.", e);
+            forceClose();
+            return;
+        }
         if (getResources().getBoolean(
                 R.bool.config_auto_data_switch_enables_cross_sim_calling)) {
             // If auto data switch is already enabled on the non-DDS, the dialog for enabling it
diff --git a/src/com/android/settings/slices/RestrictedSliceUtils.java b/src/com/android/settings/slices/RestrictedSliceUtils.java
new file mode 100644
index 0000000..a5b5a14
--- /dev/null
+++ b/src/com/android/settings/slices/RestrictedSliceUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.slices;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.SettingsSlicesContract;
+
+/**
+ * A utility class to check slice Uris for restriction.
+ */
+public class RestrictedSliceUtils {
+
+    /**
+     * Uri for the notifying open networks Slice.
+     */
+    private static final Uri NOTIFY_OPEN_NETWORKS_SLICE_URI = new Uri.Builder()
+        .scheme(ContentResolver.SCHEME_CONTENT)
+        .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+        .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+        .appendPath("notify_open_networks")
+        .build();
+
+    /**
+     * Uri for the auto turning on Wi-Fi Slice.
+     */
+    private static final Uri AUTO_TURN_ON_WIFI_SLICE_URI = new Uri.Builder()
+        .scheme(ContentResolver.SCHEME_CONTENT)
+        .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+        .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+        .appendPath("enable_wifi_wakeup")
+        .build();
+
+    /**
+     * Uri for the usb tethering Slice.
+     */
+    private static final Uri USB_TETHERING_SLICE_URI = new Uri.Builder()
+        .scheme(ContentResolver.SCHEME_CONTENT)
+        .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+        .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+        .appendPath("enable_usb_tethering")
+        .build();
+
+    /**
+     * Uri for the bluetooth tethering Slice.
+     */
+    private static final Uri BLUETOOTH_TETHERING_SLICE_URI = new Uri.Builder()
+        .scheme(ContentResolver.SCHEME_CONTENT)
+        .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+        .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+        .appendPath("enable_bluetooth_tethering_2")
+        .build();
+
+    /**
+     * Returns true if the slice Uri restricts access to guest user.
+     */
+    public static boolean isGuestRestricted(Uri sliceUri) {
+        if (AUTO_TURN_ON_WIFI_SLICE_URI.equals(sliceUri)
+            || NOTIFY_OPEN_NETWORKS_SLICE_URI.equals(sliceUri)
+            || BLUETOOTH_TETHERING_SLICE_URI.equals(sliceUri)
+            || USB_TETHERING_SLICE_URI.equals(sliceUri)
+            || CustomSliceRegistry.MOBILE_DATA_SLICE_URI.equals(sliceUri)) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 12272a7..5d2bde3 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -30,6 +30,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.StrictMode;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.SettingsSlicesContract;
 import android.text.TextUtils;
@@ -233,6 +234,14 @@
                 getContext().getTheme().rebase();
             }
 
+            // Checking if some semi-sensitive slices are requested by a guest user. If so, will
+            // return an empty slice.
+            final UserManager userManager = getContext().getSystemService(UserManager.class);
+            if (userManager.isGuestUser() && RestrictedSliceUtils.isGuestRestricted(sliceUri)) {
+                Log.i(TAG, "Guest user access denied.");
+                return null;
+            }
+
             // Before adding a slice to {@link CustomSliceManager}, please get approval
             // from the Settings team.
             if (CustomSliceRegistry.isValidUri(sliceUri)) {
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 455fe9f..db88784 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -20,6 +20,7 @@
 import android.util.FeatureFlagUtils
 import com.android.settings.spa.app.AllAppListPageProvider
 import com.android.settings.spa.app.AppsMainPageProvider
+import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
 import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider
 import com.android.settings.spa.app.backgroundinstall.BackgroundInstalledAppsPageProvider
@@ -29,12 +30,14 @@
 import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
 import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
 import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
+import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
 import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
 import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
 import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
 import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider
 import com.android.settings.spa.core.instrumentation.SpaLogProvider
 import com.android.settings.spa.development.UsageStatsPageProvider
+import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider
 import com.android.settings.spa.home.HomePageProvider
 import com.android.settings.spa.network.NetworkAndInternetPageProvider
 import com.android.settings.spa.notification.AppListNotificationsPageProvider
@@ -61,6 +64,7 @@
             InstallUnknownAppsListProvider,
             AlarmsAndRemindersAppListProvider,
             WifiControlAppListProvider,
+            NfcTagAppsSettingsProvider,
         )
     }
 
@@ -81,7 +85,9 @@
                 LanguageAndInputPageProvider,
                 AppLanguagesPageProvider,
                 UsageStatsPageProvider,
+                PlatformCompatAppListPageProvider,
                 BackgroundInstalledAppsPageProvider,
+                UserAspectRatioAppsPageProvider,
                 CloneAppInfoSettingsProvider,
                 NetworkAndInternetPageProvider,
                 ) + togglePermissionAppListTemplate.createPageProviders(),
@@ -93,5 +99,5 @@
     override val logger =
         if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_ENABLE_SPA_METRICS))
             SpaLogProvider
-        else object: SpaLogger {}
+        else object : SpaLogger {}
 }
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
new file mode 100644
index 0000000..5206343
--- /dev/null
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appcompat
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settings.R
+import com.android.settings.applications.appcompat.UserAspectRatioDetails
+import com.android.settings.applications.appcompat.UserAspectRatioManager
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+@Composable
+fun UserAspectRatioAppPreference(app: ApplicationInfo) {
+    val context = LocalContext.current
+    val presenter = remember { UserAspectRatioAppPresenter(context, app) }
+    if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+
+    Preference(object : PreferenceModel {
+        override val title = stringResource(R.string.aspect_ratio_title)
+        override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
+            initialValue = stringResource(R.string.summary_placeholder),
+        )
+        override val onClick = presenter::startActivity
+    })
+}
+
+class UserAspectRatioAppPresenter(
+    private val context: Context,
+    private val app: ApplicationInfo,
+) {
+    private val manager = UserAspectRatioManager(context)
+
+    val isAvailableFlow = flow {
+        emit(UserAspectRatioManager.isFeatureEnabled(context)
+                && manager.canDisplayAspectRatioUi(app))
+    }.flowOn(Dispatchers.IO)
+
+    fun startActivity() =
+        navigateToAppAspectRatioSettings(context, app)
+
+    val summaryFlow = flow {
+        emit(manager.getUserMinAspectRatioEntry(app.packageName, context.userId))
+    }.flowOn(Dispatchers.IO)
+}
+
+fun navigateToAppAspectRatioSettings(context: Context, app: ApplicationInfo) {
+    AppInfoDashboardFragment.startAppInfoFragment(
+        UserAspectRatioDetails::class.java,
+        app,
+        context,
+        AppInfoSettingsProvider.METRICS_CATEGORY,
+    )
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
new file mode 100644
index 0000000..e0c778d
--- /dev/null
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appcompat
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.settings.R
+import com.android.settings.applications.appcompat.UserAspectRatioManager
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.util.asyncMap
+import com.android.settingslib.spa.framework.util.filterItem
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
+import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spa.widget.ui.SpinnerOption
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.template.app.AppList
+import com.android.settingslib.spaprivileged.template.app.AppListInput
+import com.android.settingslib.spaprivileged.template.app.AppListItem
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.android.settingslib.spaprivileged.template.app.AppListPage
+import com.google.common.annotations.VisibleForTesting
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+object UserAspectRatioAppsPageProvider : SettingsPageProvider {
+    override val name = "UserAspectRatioAppsPage"
+    private val owner = createSettingsPage()
+
+    override fun isEnabled(arguments: Bundle?): Boolean =
+        UserAspectRatioManager.isFeatureEnabled(SpaEnvironmentFactory.instance.appContext)
+
+    @Composable
+    override fun Page(arguments: Bundle?) =
+        UserAspectRatioAppList()
+
+    @Composable
+    @VisibleForTesting
+    fun EntryItem() =
+        Preference(object : PreferenceModel {
+            override val title = stringResource(R.string.aspect_ratio_title)
+            override val summary = getSummary().toState()
+            override val onClick = navigator(name)
+        })
+
+    @VisibleForTesting
+    fun buildInjectEntry() = SettingsEntryBuilder
+        .createInject(owner)
+        .setSearchDataFn { null }
+        .setUiLayoutFn { EntryItem() }
+
+    @Composable
+    @VisibleForTesting
+    fun getSummary(): String = stringResource(R.string.aspect_ratio_summary, Build.MODEL)
+}
+
+@Composable
+fun UserAspectRatioAppList(
+    appList: @Composable AppListInput<UserAspectRatioAppListItemModel>.() -> Unit
+    = { AppList() },
+) {
+    AppListPage(
+        title = stringResource(R.string.aspect_ratio_title),
+        listModel = rememberContext(::UserAspectRatioAppListModel),
+        appList = appList,
+        header = {
+            Box(Modifier.padding(SettingsDimension.itemPadding)) {
+                SettingsBody(UserAspectRatioAppsPageProvider.getSummary())
+            }
+            Illustration(object : IllustrationModel {
+                override val resId = R.raw.user_aspect_ratio_education
+                override val resourceType = ResourceType.LOTTIE
+            })
+        }
+    )
+}
+
+data class UserAspectRatioAppListItemModel(
+    override val app: ApplicationInfo,
+    val userOverride: Int,
+    val suggested: Boolean,
+    val canDisplay: Boolean,
+) : AppRecord
+
+class UserAspectRatioAppListModel(private val context: Context)
+    : AppListModel<UserAspectRatioAppListItemModel> {
+
+    private val packageManager = context.packageManager
+    private val userAspectRatioManager = UserAspectRatioManager(context)
+
+    override fun getSpinnerOptions(
+        recordList: List<UserAspectRatioAppListItemModel>
+    ): List<SpinnerOption> {
+        val hasSuggested = recordList.any { it.suggested }
+        val hasOverride = recordList.any { it.userOverride != USER_MIN_ASPECT_RATIO_UNSET }
+        val options = mutableListOf(SpinnerItem.All)
+        // Add suggested filter first as default
+        if (hasSuggested) options.add(0, SpinnerItem.Suggested)
+        if (hasOverride) options += SpinnerItem.Overridden
+        return options.map {
+            SpinnerOption(
+                id = it.ordinal,
+                text = context.getString(it.stringResId),
+            )
+        }
+    }
+
+    @Composable
+    override fun AppListItemModel<UserAspectRatioAppListItemModel>.AppItem() {
+        val app = record.app
+        AppListItem(
+            onClick = { navigateToAppAspectRatioSettings(context, app) }
+        )
+    }
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        userIdFlow.combine(appListFlow) { uid, appList ->
+            appList.asyncMap { app ->
+                UserAspectRatioAppListItemModel(
+                    app = app,
+                    suggested = !app.isSystemApp && getPackageAndActivityInfo(
+                                    app)?.isFixedOrientationOrAspectRatio() == true,
+                    userOverride = userAspectRatioManager.getUserMinAspectRatioValue(
+                                    app.packageName, uid),
+                    canDisplay = userAspectRatioManager.canDisplayAspectRatioUi(app),
+                )
+            }
+        }
+
+    override fun filter(
+        userIdFlow: Flow<Int>,
+        option: Int,
+        recordListFlow: Flow<List<UserAspectRatioAppListItemModel>>
+    ): Flow<List<UserAspectRatioAppListItemModel>> = recordListFlow.filterItem(
+        when (SpinnerItem.values().getOrNull(option)) {
+            SpinnerItem.Suggested -> ({ it.canDisplay && it.suggested })
+            SpinnerItem.Overridden -> ({ it.userOverride != USER_MIN_ASPECT_RATIO_UNSET })
+            else -> ({ it.canDisplay })
+        }
+    )
+
+    @Composable
+    override fun getSummary(option: Int, record: UserAspectRatioAppListItemModel) : State<String> =
+        remember(record.userOverride) {
+            flow {
+                emit(userAspectRatioManager.getUserMinAspectRatioEntry(record.userOverride,
+                    record.app.packageName))
+            }.flowOn(Dispatchers.IO)
+        }.collectAsStateWithLifecycle(initialValue = stringResource(R.string.summary_placeholder))
+
+    private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try {
+        packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
+    } catch (e: Exception) {
+        // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause
+        // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture
+        // packages, we need to catch the exception to alleviate the impact before PackageManager
+        // fixing this issue or provide a better api.
+        Log.e(TAG, "Exception while getPackageInfoAsUser", e)
+        null
+    }
+
+    companion object {
+        private const val TAG = "AspectRatioAppsListModel"
+        private fun PackageInfo.isFixedOrientationOrAspectRatio() =
+            activities?.any { a -> a.isFixedOrientation || a.hasFixedAspectRatio() } ?: false
+        private val GET_ACTIVITIES_FLAGS =
+            PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+    }
+}
+
+private enum class SpinnerItem(val stringResId: Int) {
+    Suggested(R.string.user_aspect_ratio_suggested_apps_label),
+    All(R.string.filter_all_apps),
+    Overridden(R.string.user_aspect_ratio_overridden_apps_label)
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt b/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt
index 29c7483..34272d4 100644
--- a/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt
@@ -28,7 +28,6 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settingslib.spa.widget.preference.Preference
@@ -46,7 +45,6 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.plus
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppAllServicesPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index b93c12a..3200b81 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -19,7 +19,6 @@
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.applications.AppUtils
 import com.android.settingslib.spa.widget.button.ActionButton
@@ -43,7 +42,6 @@
     private val appClearButton = AppClearButton(packageInfoPresenter)
     private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
 
-    @OptIn(ExperimentalLifecycleComposeApi::class)
     @Composable
     fun getActionButtons() =
         packageInfoPresenter.flow.collectAsStateWithLifecycle(initialValue = null).value?.let {
diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
index 8b90280..5210dc7 100644
--- a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
@@ -27,7 +27,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settings.Utils
@@ -44,7 +43,6 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.withContext
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppDataUsagePreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 8cb48fb..a9d16ae 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -27,7 +27,6 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
@@ -35,6 +34,7 @@
 import com.android.settings.applications.AppInfoBase
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
 import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+import com.android.settings.spa.app.appcompat.UserAspectRatioAppPreference
 import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
 import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
 import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
@@ -114,7 +114,6 @@
     }
 }
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
     LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
@@ -150,6 +149,7 @@
         }
 
         Category(title = stringResource(R.string.advanced_apps)) {
+            UserAspectRatioAppPreference(app)
             DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
             ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
             PictureInPictureListProvider.InfoPageEntryItem(app)
diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
index b89d768..221add9 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
@@ -27,7 +27,6 @@
 import com.android.settings.Utils
 import com.android.settings.applications.AppStoreUtil
 import com.android.settingslib.applications.AppUtils
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -45,7 +44,6 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppInstallerInfoPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt b/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt
index e95c5a1..2d6fbb6 100644
--- a/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt
@@ -31,7 +31,6 @@
 import com.android.settings.applications.AppLocaleUtil
 import com.android.settings.applications.appinfo.AppLocaleDetails
 import com.android.settings.localepicker.AppLocalePickerActivity
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -41,7 +40,6 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.withContext
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppLocalePreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
index 490a98c..45033e7 100644
--- a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
@@ -38,7 +37,6 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppNotificationPreference(
     app: ApplicationInfo,
diff --git a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
index 2c98e08..757ddc2 100644
--- a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
@@ -41,7 +40,6 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppOpenByDefaultPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt b/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt
index 09957ca..0a10b23 100644
--- a/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppSettingsPreference.kt
@@ -28,7 +28,6 @@
 import androidx.compose.ui.res.stringResource
 import com.android.settings.R
 import com.android.settings.overlay.FeatureFactory
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -44,7 +43,6 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.plus
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun AppSettingsPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
index 982c974..760d375 100644
--- a/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
@@ -23,7 +23,6 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
@@ -69,7 +68,6 @@
     fun getRoute(packageName: String, userId: Int): String = "$name/$packageName/$userId"
 }
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 private fun CloneAppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
     LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
diff --git a/src/com/android/settings/spa/app/appinfo/ClonePageAppButtons.kt b/src/com/android/settings/spa/app/appinfo/ClonePageAppButtons.kt
index 09697bf..ddc7e17 100644
--- a/src/com/android/settings/spa/app/appinfo/ClonePageAppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/ClonePageAppButtons.kt
@@ -22,7 +22,6 @@
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settingslib.spa.widget.button.ActionButton
@@ -39,7 +38,6 @@
     private val appCreateButton = AppCreateButton(packageInfoPresenter)
     private val appForceStopButton = FakeAppForceStopButton(packageInfoPresenter)
 
-    @OptIn(ExperimentalLifecycleComposeApi::class)
     @Composable
     fun getActionButtons() =
         packageInfoPresenter.flow.collectAsStateWithLifecycle(initialValue = null).value?.let {
diff --git a/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt b/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt
index fa7e089..74c0aa4 100644
--- a/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt
@@ -26,7 +26,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.liveData
 import com.android.settings.R
@@ -49,7 +48,6 @@
     @StringRes val titleResId: Int,
 )
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun DefaultAppShortcutPreference(shortcut: DefaultAppShortcut, app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
index d981067..f62a3be 100644
--- a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
@@ -35,7 +35,6 @@
 import com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED
 import com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS
 import com.android.settingslib.spa.framework.compose.OverridableFlow
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.preference.SwitchPreference
@@ -52,7 +51,6 @@
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun HibernationSwitchPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt b/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
index 0e93b56..7b9480d 100644
--- a/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
@@ -34,7 +34,6 @@
 import androidx.compose.ui.res.stringResource
 import com.android.settings.R
 import com.android.settings.Utils
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.preference.Preference
@@ -46,7 +45,6 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun InstantAppDomainsPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt b/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt
index 2430e73..12f6907 100644
--- a/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt
@@ -25,7 +25,6 @@
 import com.android.settings.R
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
 import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetails
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -34,7 +33,6 @@
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 
-@OptIn(ExperimentalLifecycleComposeApi::class)
 @Composable
 fun InteractAcrossProfilesDetailsPreference(app: ApplicationInfo) {
     val context = LocalContext.current
diff --git a/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt
new file mode 100644
index 0000000..3dede42
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.specialaccess
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.content.pm.PackageManager.PackageInfoFlags
+import android.nfc.NfcAdapter
+import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object NfcTagAppsSettingsProvider : TogglePermissionAppListProvider {
+    override val permissionType = "NfcTagAppsSettings"
+    override fun createModel(context: Context) = NfcTagAppsSettingsListModel(context)
+}
+
+data class NfcTagAppsSettingsRecord(
+    override val app: ApplicationInfo,
+    val controller: NfcTagAppsSettingsController,
+    val isSupported: Boolean,
+) : AppRecord
+
+class NfcTagAppsSettingsListModel(private val context: Context) :
+    TogglePermissionAppListModel<NfcTagAppsSettingsRecord> {
+    override val pageTitleResId = R.string.change_nfc_tag_apps_title
+    override val switchTitleResId = R.string.change_nfc_tag_apps_detail_switch
+    override val footerResId = R.string.change_nfc_tag_apps_detail_summary
+
+    private val packageManager = context.packageManager
+
+    override fun transform(
+        userIdFlow: Flow<Int>,
+        appListFlow: Flow<List<ApplicationInfo>>
+    ): Flow<List<NfcTagAppsSettingsRecord>> =
+        userIdFlow.combine(appListFlow) { userId, appList ->
+            // The appListFlow always refreshed on resume, need to update nfcTagAppsSettingsPackages
+            // here to handle status change.
+            val nfcTagAppsSettingsPackages = getNfcTagAppsSettingsPackages(userId)
+            appList.map { app ->
+                createNfcTagAppsSettingsRecord(
+                    app = app,
+                    isAllowed = nfcTagAppsSettingsPackages[app.packageName],
+                )
+            }
+        }
+
+    private fun getNfcTagAppsSettingsPackages(userId: Int): Map<String, Boolean> {
+        NfcAdapter.getDefaultAdapter(context)?.let { nfcAdapter ->
+            if (nfcAdapter.isTagIntentAppPreferenceSupported) {
+                return nfcAdapter.getTagIntentAppPreferenceForUser(userId)
+            }
+        }
+        return emptyMap()
+    }
+
+    override fun transformItem(app: ApplicationInfo) =
+        createNfcTagAppsSettingsRecord(
+            app = app,
+            isAllowed = getNfcTagAppsSettingsPackages(app.userId)[app.packageName],
+        )
+
+    private fun createNfcTagAppsSettingsRecord(
+        app: ApplicationInfo,
+        isAllowed: Boolean?,
+    ) =
+        NfcTagAppsSettingsRecord(
+            app = app,
+            isSupported = isAllowed != null,
+            controller = NfcTagAppsSettingsController(isAllowed == true),
+        )
+
+    override fun filter(
+        userIdFlow: Flow<Int>,
+        recordListFlow: Flow<List<NfcTagAppsSettingsRecord>>
+    ) = recordListFlow.map { recordList -> recordList.filter { it.isSupported } }
+
+    @Composable
+    override fun isAllowed(record: NfcTagAppsSettingsRecord) =
+        record.controller.isAllowed.observeAsState()
+
+    override fun isChangeable(record: NfcTagAppsSettingsRecord) = true
+
+    override fun setAllowed(record: NfcTagAppsSettingsRecord, newAllowed: Boolean) {
+        NfcAdapter.getDefaultAdapter(context)?.let {
+            if (
+                it.setTagIntentAppPreferenceForUser(
+                    record.app.userId,
+                    record.app.packageName,
+                    newAllowed
+                ) == NfcAdapter.TAG_INTENT_APP_PREF_RESULT_SUCCESS
+            ) {
+                record.controller.setAllowed(newAllowed)
+            } else {
+                Log.e(TAG, "Error updating TagIntentAppPreference")
+            }
+        }
+    }
+
+    private companion object {
+        const val TAG = "NfcTagAppsSettingsListModel"
+        val GET_ACTIVITIES_FLAGS = PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+    }
+}
diff --git a/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettingsController.kt b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettingsController.kt
new file mode 100644
index 0000000..6e1b7b3
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettingsController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.specialaccess
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+class NfcTagAppsSettingsController(initialStatus: Boolean) {
+    val isAllowed: LiveData<Boolean>
+        get() = _allowed
+
+    fun setAllowed(newAllowed: Boolean) {
+        _allowed.postValue(newAllowed)
+    }
+    private val _allowed = MutableLiveData<Boolean>(initialStatus)
+}
diff --git a/src/com/android/settings/spa/development/UsageStats.kt b/src/com/android/settings/spa/development/UsageStats.kt
index b681d75..4d9c455 100644
--- a/src/com/android/settings/spa/development/UsageStats.kt
+++ b/src/com/android/settings/spa/development/UsageStats.kt
@@ -32,7 +32,6 @@
         AppListPage(
             title = stringResource(R.string.testing_usage_stats),
             listModel = rememberContext(::UsageStatsListModel),
-            primaryUserOnly = true,
         )
     }
 }
diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt
new file mode 100644
index 0000000..5f3b4e7
--- /dev/null
+++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppList.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.development.compat
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spaprivileged.template.app.AppListPage
+
+object PlatformCompatAppListPageProvider : SettingsPageProvider {
+    override val name = "PlatformCompatAppList"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        AppListPage(
+            title = stringResource(R.string.platform_compat_dashboard_title),
+            listModel = rememberContext(::PlatformCompatAppListModel),
+            noItemMessage = stringResource(R.string.platform_compat_dialog_text_no_apps),
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt
new file mode 100644
index 0000000..c6752b9
--- /dev/null
+++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.development.compat
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.Build
+import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import com.android.settings.core.SubSettingLauncher
+import com.android.settings.development.compat.PlatformCompatDashboard
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.filterItem
+import com.android.settingslib.spa.framework.util.mapItem
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.hasFlag
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.template.app.AppListItem
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import kotlinx.coroutines.flow.Flow
+
+data class PlatformCompatAppRecord(
+    override val app: ApplicationInfo,
+) : AppRecord
+
+class PlatformCompatAppListModel(
+    private val context: Context,
+) : AppListModel<PlatformCompatAppRecord> {
+
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.mapItem(::PlatformCompatAppRecord)
+
+    override fun filter(
+        userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<PlatformCompatAppRecord>>,
+    ) = recordListFlow.filterItem { record ->
+        Build.IS_DEBUGGABLE || record.app.hasFlag(ApplicationInfo.FLAG_DEBUGGABLE)
+    }
+
+    @Composable
+    override fun getSummary(option: Int, record: PlatformCompatAppRecord) =
+        stateOf(record.app.packageName)
+
+    @Composable
+    override fun AppListItemModel<PlatformCompatAppRecord>.AppItem() {
+        AppListItem { navigateToAppCompat(app = record.app) }
+    }
+
+    private fun navigateToAppCompat(app: ApplicationInfo) {
+        SubSettingLauncher(context)
+            .setDestination(PlatformCompatDashboard::class.qualifiedName)
+            .setSourceMetricsCategory(SettingsEnums.DEVELOPMENT)
+            .setArguments(bundleOf(PlatformCompatDashboard.COMPAT_APP to app.packageName))
+            .setUserHandle(app.userHandle)
+            .launch()
+    }
+}
diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt b/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt
new file mode 100644
index 0000000..c0a421c
--- /dev/null
+++ b/src/com/android/settings/spa/development/compat/PlatformCompatPreferenceController.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.spa.development.compat
+
+import android.content.Context
+import androidx.preference.Preference
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+
+class PlatformCompatPreferenceController(context: Context, preferenceKey: String) :
+    BasePreferenceController(context, preferenceKey) {
+    override fun getAvailabilityStatus() = AVAILABLE
+
+    override fun handlePreferenceTreeClick(preference: Preference): Boolean {
+        if (preference.key == mPreferenceKey) {
+            mContext.startSpaActivity(PlatformCompatAppListPageProvider.name)
+            return true
+        }
+        return false
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/system/AppLanguagesListModel.kt b/src/com/android/settings/spa/system/AppLanguagesListModel.kt
index 942bcc4..3413ff0 100644
--- a/src/com/android/settings/spa/system/AppLanguagesListModel.kt
+++ b/src/com/android/settings/spa/system/AppLanguagesListModel.kt
@@ -26,7 +26,6 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.ui.res.stringResource
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settings.applications.AppLocaleUtil
@@ -79,7 +78,6 @@
         recordListFlow: Flow<List<AppLanguagesRecord>>,
     ) = recordListFlow.filterItem { it.isAppLocaleSupported }
 
-    @OptIn(ExperimentalLifecycleComposeApi::class)
     @Composable
     override fun getSummary(option: Int, record: AppLanguagesRecord): State<String> =
         remember(record.app) {
diff --git a/src/com/android/settings/users/GuestTelephonyPreferenceController.java b/src/com/android/settings/users/GuestTelephonyPreferenceController.java
index a935b8a..83e4bfc 100644
--- a/src/com/android/settings/users/GuestTelephonyPreferenceController.java
+++ b/src/com/android/settings/users/GuestTelephonyPreferenceController.java
@@ -17,6 +17,7 @@
 package com.android.settings.users;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.UserManager;
 
@@ -33,14 +34,11 @@
 
     private final UserManager mUserManager;
     private final UserCapabilities mUserCaps;
-    private Bundle mDefaultGuestRestrictions;
 
     public GuestTelephonyPreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
         mUserManager = context.getSystemService(UserManager.class);
         mUserCaps = UserCapabilities.create(context);
-        mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
-        mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
     }
 
     @Override
@@ -54,13 +52,16 @@
 
     @Override
     public boolean isChecked() {
-        return !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false);
+        return !mUserManager.getDefaultGuestRestrictions()
+                .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS, false);
     }
 
     @Override
     public boolean setChecked(boolean isChecked) {
-        mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !isChecked);
-        mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions);
+        Bundle guestRestrictions = mUserManager.getDefaultGuestRestrictions();
+        guestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+        guestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !isChecked);
+        mUserManager.setDefaultGuestRestrictions(guestRestrictions);
         return true;
     }
 
@@ -73,6 +74,7 @@
     public void updateState(Preference preference) {
         super.updateState(preference);
         mUserCaps.updateAddUserCapabilities(mContext);
-        preference.setVisible(isAvailable() && mUserCaps.mUserSwitcherEnabled);
+        preference.setVisible(isAvailable() && mUserCaps.mUserSwitcherEnabled
+                && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
     }
 }
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index 2f9031e..402d4b1 100644
--- a/src/com/android/settings/users/UserDetailsSettings.java
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -79,6 +79,7 @@
 
     /** Whether to enable the app_copying fragment. */
     private static final boolean SHOW_APP_COPYING_PREF = false;
+    private static final int MESSAGE_PADDING = 20;
 
     private UserManager mUserManager;
     private UserCapabilities mUserCaps;
@@ -274,6 +275,7 @@
                 context.getDrawable(com.android.settingslib.R.drawable.ic_admin_panel_settings));
         dialogHelper.setTitle(R.string.user_revoke_admin_confirm_title);
         dialogHelper.setMessage(R.string.user_revoke_admin_confirm_message);
+        dialogHelper.setMessagePadding(MESSAGE_PADDING);
         dialogHelper.setPositiveButton(R.string.remove, view -> {
             updateUserAdminStatus(false);
             dialogHelper.getDialog().dismiss();
@@ -294,6 +296,7 @@
                 context.getDrawable(com.android.settingslib.R.drawable.ic_admin_panel_settings));
         dialogHelper.setTitle(com.android.settingslib.R.string.user_grant_admin_title);
         dialogHelper.setMessage(com.android.settingslib.R.string.user_grant_admin_message);
+        dialogHelper.setMessagePadding(MESSAGE_PADDING);
         dialogHelper.setPositiveButton(com.android.settingslib.R.string.user_grant_admin_button,
                 view -> {
                     updateUserAdminStatus(true);
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index 28e02ec..b0816fd 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -885,7 +885,6 @@
                 this::startActivityForResult,
                 userIcon,
                 user.name,
-                getString(com.android.settingslib.R.string.profile_info_settings_title),
                 (newUserName, newUserIcon) -> {
                     if (newUserIcon != userIcon) {
                         ThreadUtils.postOnBackgroundThread(() ->
@@ -978,10 +977,10 @@
             return;
         }
         try {
-            getContext().getSystemService(UserManager.class)
-                    .removeUserWhenPossible(UserHandle.of(UserHandle.myUserId()),
-                            /* overrideDevicePolicy= */ false);
-            ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
+            mUserManager.removeUserWhenPossible(
+                    UserHandle.of(UserHandle.myUserId()), /* overrideDevicePolicy= */ false);
+            ActivityManager.getService().switchUser(
+                    mUserManager.getPreviousForegroundUser().getIdentifier());
         } catch (RemoteException re) {
             Log.e(TAG, "Unable to remove self user");
         }
@@ -1100,7 +1099,7 @@
         }
         mMetricsFeatureProvider.action(getActivity(),
                 SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED);
-        switchToUserId(UserHandle.USER_SYSTEM);
+        switchToUserId(mUserManager.getPreviousForegroundUser().getIdentifier());
     }
 
     private int createGuest() {
@@ -1140,8 +1139,8 @@
             // Create a new guest in the foreground, and then immediately switch to it
             int newGuestUserId = createGuest();
             if (newGuestUserId == UserHandle.USER_NULL) {
-                Log.e(TAG, "Could not create new guest, switching back to system user");
-                switchToUserId(UserHandle.USER_SYSTEM);
+                Log.e(TAG, "Could not create new guest, switching back to previous user");
+                switchToUserId(mUserManager.getPreviousForegroundUser().getIdentifier());
                 mUserManager.removeUser(oldGuestUserId);
                 WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
                 return;
@@ -1629,7 +1628,7 @@
             mRemovingUserId = -1;
             updateUserList();
             if (mCreateUserDialogController.isActive()) {
-                mCreateUserDialogController.clear();
+                mCreateUserDialogController.finish();
             }
         }
     }
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
index a91bb6c..8cec2f4 100644
--- a/src/com/android/settings/vpn2/VpnSettings.java
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -61,7 +61,7 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.settings.R;
-import com.android.settings.RestrictedSettingsFragment;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.GearPreference;
 import com.android.settings.widget.GearPreference.OnGearClickListener;
@@ -80,7 +80,7 @@
  * Settings screen listing VPNs. Configured VPNs and networks managed by apps
  * are shown in the same list.
  */
-public class VpnSettings extends RestrictedSettingsFragment implements
+public class VpnSettings extends RestrictedDashboardFragment implements
         Handler.Callback, Preference.OnPreferenceClickListener {
     private static final String LOG_TAG = "VpnSettings";
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
@@ -135,7 +135,6 @@
         mUnavailable = isUiRestricted();
         setHasOptionsMenu(!mUnavailable);
 
-        addPreferencesFromResource(R.xml.vpn_settings2);
         mPreferenceScreen = getPreferenceScreen();
     }
 
@@ -212,6 +211,16 @@
     }
 
     @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.vpn_settings2;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return LOG_TAG;
+    }
+
+    @Override
     public void onPause() {
         if (mUnavailable) {
             super.onPause();
diff --git a/src/com/android/settings/wifi/LongPressWifiEntryPreference.java b/src/com/android/settings/wifi/LongPressWifiEntryPreference.java
index 6343e06..ec94e74 100644
--- a/src/com/android/settings/wifi/LongPressWifiEntryPreference.java
+++ b/src/com/android/settings/wifi/LongPressWifiEntryPreference.java
@@ -22,6 +22,7 @@
 import androidx.fragment.app.Fragment;
 import androidx.preference.PreferenceViewHolder;
 
+import com.android.settingslib.RestrictedLockUtils;
 import com.android.wifitrackerlib.WifiEntry;
 
 /**
@@ -34,7 +35,7 @@
     public LongPressWifiEntryPreference(Context context, WifiEntry wifiEntry, Fragment fragment) {
         super(context, wifiEntry);
         mFragment = fragment;
-        checkRestrictionAndSetDisabled(UserManager.DISALLOW_ADD_WIFI_CONFIG);
+        checkRestrictionAndSetDisabled();
     }
 
     @Override
@@ -65,4 +66,22 @@
         }
         return enabled;
     }
+
+    @VisibleForTesting
+    void checkRestrictionAndSetDisabled() {
+        if (!getWifiEntry().hasAdminRestrictions()) {
+            return;
+        }
+        RestrictedLockUtils.EnforcedAdmin admin = null;
+        Context context = getContext();
+        if (context != null) {
+            admin = RestrictedLockUtils.getProfileOrDeviceOwner(context, context.getUser());
+        }
+        if (admin == null) {
+            // Use UserManager.DISALLOW_ADD_WIFI_CONFIG as default Wi-Fi network restriction.
+            admin = RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                    UserManager.DISALLOW_ADD_WIFI_CONFIG);
+        }
+        setDisabledByAdmin(admin);
+    }
 }
diff --git a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
index 5639047..93d88e9 100644
--- a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
+++ b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
@@ -18,8 +18,6 @@
 
 import static com.android.wifitrackerlib.Utils.getSecurityTypesFromScanResult;
 
-import static java.util.stream.Collectors.toList;
-
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -273,19 +271,31 @@
     @VisibleForTesting
     void updateWifiEntries() {
         final List<WifiEntry> wifiEntries = new ArrayList<>();
-        if (mWifiPickerTracker.getConnectedWifiEntry() != null) {
-            wifiEntries.add(mWifiPickerTracker.getConnectedWifiEntry());
+        WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
+        String connectedSsid;
+        if (connectedWifiEntry != null) {
+            connectedSsid = connectedWifiEntry.getSsid();
+            wifiEntries.add(connectedWifiEntry);
+        } else {
+            connectedSsid = null;
         }
         wifiEntries.addAll(mWifiPickerTracker.getWifiEntries());
 
         mFilteredWifiEntries.clear();
         mFilteredWifiEntries.addAll(wifiEntries.stream()
-                .filter(entry -> isMatchedWifiEntry(entry))
+                .filter(entry -> isMatchedWifiEntry(entry, connectedSsid))
                 .limit(mShowLimitedItem ? MAX_NUMBER_LIST_ITEM : Long.MAX_VALUE)
-                .collect(toList()));
+                .toList());
     }
 
-    private boolean isMatchedWifiEntry(WifiEntry entry) {
+    private boolean isMatchedWifiEntry(WifiEntry entry, String connectedSsid) {
+        if (entry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED
+                && TextUtils.equals(entry.getSsid(), connectedSsid)) {
+            // WifiPickerTracker may return a duplicate unsaved network that is separate from
+            // the connecting app-requested network, so make sure we only show the connected
+            // app-requested one.
+            return false;
+        }
         for (MatchWifi wifi : mMatchWifis) {
             if (!TextUtils.equals(entry.getSsid(), wifi.mSsid)) {
                 continue;
diff --git a/src/com/android/settings/wifi/WifiAPITest.java b/src/com/android/settings/wifi/WifiAPITest.java
index 15465ed..c8bcf7f 100644
--- a/src/com/android/settings/wifi/WifiAPITest.java
+++ b/src/com/android/settings/wifi/WifiAPITest.java
@@ -69,7 +69,7 @@
 
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        addPreferencesFromResource(R.layout.wifi_api_test);
+        addPreferencesFromResource(R.xml.wifi_api_test);
 
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
 
diff --git a/src/com/android/settings/wifi/WifiEntryPreference.java b/src/com/android/settings/wifi/WifiEntryPreference.java
index 5b44887..7206666 100644
--- a/src/com/android/settings/wifi/WifiEntryPreference.java
+++ b/src/com/android/settings/wifi/WifiEntryPreference.java
@@ -15,6 +15,8 @@
  */
 package com.android.settings.wifi;
 
+import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
@@ -37,6 +39,7 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.wifitrackerlib.BaseWifiTracker;
+import com.android.wifitrackerlib.HotspotNetworkEntry;
 import com.android.wifitrackerlib.WifiEntry;
 
 /**
@@ -145,13 +148,17 @@
      */
     public void refresh() {
         setTitle(mWifiEntry.getTitle());
-        final int level = mWifiEntry.getLevel();
-        final boolean showX = mWifiEntry.shouldShowXLevelIcon();
-        if (level != mLevel || showX != mShowX) {
-            mLevel = level;
-            mShowX = showX;
-            updateIcon(mShowX, mLevel);
-            notifyChanged();
+        if (mWifiEntry instanceof HotspotNetworkEntry) {
+            updateHotspotIcon(((HotspotNetworkEntry) mWifiEntry).getDeviceType());
+        } else {
+            int level = mWifiEntry.getLevel();
+            boolean showX = mWifiEntry.shouldShowXLevelIcon();
+
+            if (level != mLevel || showX != mShowX) {
+                mLevel = level;
+                mShowX = showX;
+                updateIcon(mShowX, mLevel);
+            }
         }
 
         setSummary(mWifiEntry.getSummary(false /* concise */));
@@ -201,14 +208,7 @@
         return accent ? android.R.attr.colorAccent : android.R.attr.colorControlNormal;
     }
 
-    @VisibleForTesting
-    void updateIcon(boolean showX, int level) {
-        if (level == -1) {
-            setIcon(null);
-            return;
-        }
-
-        final Drawable drawable = mIconInjector.getIcon(showX, level);
+    private void setIconWithTint(Drawable drawable) {
         if (drawable != null) {
             // Must use Drawable#setTintList() instead of Drawable#setTint() to show the grey
             // icon when the preference is disabled.
@@ -219,6 +219,20 @@
         }
     }
 
+    @VisibleForTesting
+    void updateIcon(boolean showX, int level) {
+        if (level == -1) {
+            setIcon(null);
+            return;
+        }
+        setIconWithTint(mIconInjector.getIcon(showX, level));
+    }
+
+    @VisibleForTesting
+    void updateHotspotIcon(int deviceType) {
+        setIconWithTint(getContext().getDrawable(getHotspotIconResource(deviceType)));
+    }
+
     @Nullable
     private StateListDrawable getFrictionStateListDrawable() {
         TypedArray frictionSld;
diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java
index 3890ddf..098787c 100644
--- a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java
+++ b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java
@@ -201,8 +201,10 @@
     void showAlert(Intent intent) {
         final Context context = getActivity();
 
-        final CharSequence title = intent.getCharSequenceExtra(Phone.EXTRA_KEY_ALERT_TITLE);
-        final CharSequence message = intent.getCharSequenceExtra(Phone.EXTRA_KEY_ALERT_MESSAGE);
+        final CharSequence title =
+                intent.getCharSequenceExtra(ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE);
+        final CharSequence message =
+                intent.getCharSequenceExtra(ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE);
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(context);
         builder.setMessage(message)
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index 4c5a4bf..2e1bc31 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -637,29 +637,23 @@
     }
 
     private void refreshTxSpeed() {
-        if (mWifiInfo == null
-                || mWifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) {
+        String summary = mWifiEntry.getTxSpeedString();
+        if (TextUtils.isEmpty(summary)) {
             mTxLinkSpeedPref.setVisible(false);
             return;
         }
-
-        int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
-        mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
-        mTxLinkSpeedPref.setSummary(mContext.getString(
-                R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
+        mTxLinkSpeedPref.setVisible(true);
+        mTxLinkSpeedPref.setSummary(summary);
     }
 
     private void refreshRxSpeed() {
-        if (mWifiInfo == null
-                || mWifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) {
+        String summary = mWifiEntry.getRxSpeedString();
+        if (TextUtils.isEmpty(summary)) {
             mRxLinkSpeedPref.setVisible(false);
             return;
         }
-
-        int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
-        mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
-        mRxLinkSpeedPref.setSummary(mContext.getString(
-                R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
+        mRxLinkSpeedPref.setVisible(true);
+        mRxLinkSpeedPref.setSummary(summary);
     }
 
     private void refreshSsid() {
diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
index d3a4be7..7af8343 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java
@@ -223,11 +223,9 @@
 
     private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
         final Button b = (Button) LayoutInflater.from(getContext()).inflate(
-                com.android.internal.R.layout.chooser_action_button, null);
+                R.layout.action_button, null);
         if (icon != null) {
-            final int size = getResources()
-                    .getDimensionPixelSize(
-                            com.android.internal.R.dimen.chooser_action_button_icon_size);
+            final int size = getResources().getDimensionPixelSize(R.dimen.action_button_icon_size);
             icon.setBounds(0, 0, size, size);
             b.setCompoundDrawablesRelative(icon, null, null, null);
         }
diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
index 39a5431..c336c62 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
@@ -27,11 +27,13 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.text.TextUtils;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.wifitrackerlib.WifiEntry;
 
@@ -391,11 +393,19 @@
                         }
             };
 
+            final int userId = UserHandle.myUserId();
+
             final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
-                    .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));
+                    .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title))
+                    .setUseDefaultSubtitle();
 
             if (keyguardManager.isDeviceSecure()) {
                 builder.setDeviceCredentialAllowed(true);
+                builder.setTextForDeviceCredential(
+                        null /* title */,
+                        Utils.getConfirmCredentialStringForUser(
+                                context, userId, Utils.getCredentialType(context, userId)),
+                        null /* description */);
             }
 
             final BiometricPrompt bp = builder.build();
diff --git a/src/com/android/settings/wifi/dpp/WifiQrCode.java b/src/com/android/settings/wifi/dpp/WifiQrCode.java
index 2b4c3ed..70ac96c 100644
--- a/src/com/android/settings/wifi/dpp/WifiQrCode.java
+++ b/src/com/android/settings/wifi/dpp/WifiQrCode.java
@@ -160,8 +160,9 @@
 
     private String getValueOrNull(List<String> keyValueList, String prefix) {
         for (String keyValue : keyValueList) {
-            if (keyValue.startsWith(prefix)) {
-                return  keyValue.substring(prefix.length());
+            String strippedKeyValue = keyValue.stripLeading();
+            if (strippedKeyValue.startsWith(prefix)) {
+                return strippedKeyValue.substring(prefix.length());
             }
         }
 
diff --git a/src/com/android/settings/wifi/factory/WifiFeatureProvider.java b/src/com/android/settings/wifi/factory/WifiFeatureProvider.java
index c61cf51..3f0d62f 100644
--- a/src/com/android/settings/wifi/factory/WifiFeatureProvider.java
+++ b/src/com/android/settings/wifi/factory/WifiFeatureProvider.java
@@ -26,6 +26,7 @@
 import androidx.lifecycle.ViewModelProvider;
 import androidx.lifecycle.ViewModelStoreOwner;
 
+import com.android.settings.wifi.repository.SharedConnectivityRepository;
 import com.android.settings.wifi.repository.WifiHotspotRepository;
 import com.android.settings.wifi.tether.WifiHotspotSecurityViewModel;
 import com.android.settings.wifi.tether.WifiHotspotSpeedViewModel;
@@ -44,6 +45,7 @@
     private TetheringManager mTetheringManager;
     private WifiVerboseLogging mWifiVerboseLogging;
     private WifiHotspotRepository mWifiHotspotRepository;
+    private SharedConnectivityRepository mSharedConnectivityRepository;
 
     public WifiFeatureProvider(@NonNull Context appContext) {
         mAppContext = appContext;
@@ -93,6 +95,17 @@
     }
 
     /**
+     * Gets SharedConnectivityRepository
+     */
+    public SharedConnectivityRepository getSharedConnectivityRepository() {
+        if (mSharedConnectivityRepository == null) {
+            mSharedConnectivityRepository = new SharedConnectivityRepository(mAppContext);
+            verboseLog(TAG, "getSharedConnectivityRepository():" + mSharedConnectivityRepository);
+        }
+        return mSharedConnectivityRepository;
+    }
+
+    /**
      * Gets WifiTetherViewModel
      */
     public WifiTetherViewModel getWifiTetherViewModel(@NotNull ViewModelStoreOwner owner) {
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
index c2111d6..1a268f5 100644
--- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
+++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
@@ -617,6 +617,9 @@
     }
 
     private void onDeviceAvailable() {
+        if (mWifiP2pManager == null || sChannel == null) {
+            return;
+        }
         mWifiP2pManager.requestNetworkInfo(sChannel, networkInfo -> {
             if (sChannel == null) return;
             mWifiP2pManager.requestConnectionInfo(sChannel, wifip2pinfo -> {
diff --git a/src/com/android/settings/wifi/repository/SharedConnectivityRepository.java b/src/com/android/settings/wifi/repository/SharedConnectivityRepository.java
new file mode 100644
index 0000000..5daa5f3
--- /dev/null
+++ b/src/com/android/settings/wifi/repository/SharedConnectivityRepository.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.repository;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
+import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.os.HandlerThread;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Shared Connectivity Repository for {@link SharedConnectivityManager}
+ */
+public class SharedConnectivityRepository {
+    private static final String TAG = "SharedConnectivityRepository";
+    private static final String DEVICE_CONFIG_NAMESPACE = "wifi";
+    private static final String DEVICE_CONFIG_KEY = "shared_connectivity_enabled";
+
+    private Context mAppContext;
+    private SharedConnectivityManager mManager;
+    private ClientCallback mClientCallback = new ClientCallback();
+    private HandlerThread mWorkerThread = new HandlerThread(TAG);
+    private Executor mWorkerExecutor = cmd -> mWorkerThread.getThreadHandler().post(cmd);
+    private Runnable mLaunchSettingsRunnable = () -> handleLaunchSettings();
+    @VisibleForTesting
+    MutableLiveData<SharedConnectivitySettingsState> mSettingsState = new MutableLiveData<>();
+
+    public SharedConnectivityRepository(@NonNull Context appContext) {
+        this(appContext,
+                DeviceConfig.getBoolean(DEVICE_CONFIG_NAMESPACE, DEVICE_CONFIG_KEY, false));
+    }
+
+    @VisibleForTesting
+    SharedConnectivityRepository(@NonNull Context appContext, boolean isConfigEnabled) {
+        mAppContext = appContext;
+        if (!isConfigEnabled) {
+            return;
+        }
+        mManager = mAppContext.getSystemService(SharedConnectivityManager.class);
+        if (mManager == null) {
+            Log.w(TAG, "Failed to get SharedConnectivityManager");
+            return;
+        }
+        mWorkerThread.start();
+        mManager.registerCallback(mWorkerExecutor, mClientCallback);
+    }
+
+    /**
+     * Return whether Wi-Fi Shared Connectivity service is available or not.
+     *
+     * @return {@code true} if Wi-Fi Shared Connectivity service is available
+     */
+    public boolean isServiceAvailable() {
+        return mManager != null;
+    }
+
+    /**
+     * Gets SharedConnectivitySettingsState LiveData
+     */
+    public LiveData<SharedConnectivitySettingsState> getSettingsState() {
+        return mSettingsState;
+    }
+
+    /**
+     * Launch Instant Hotspot Settings
+     */
+    public void launchSettings() {
+        mWorkerExecutor.execute(mLaunchSettingsRunnable);
+    }
+
+    @WorkerThread
+    @VisibleForTesting
+    void handleLaunchSettings() {
+        if (mManager == null) {
+            return;
+        }
+        SharedConnectivitySettingsState state = mManager.getSettingsState();
+        log("handleLaunchSettings(), state:" + state);
+        if (state == null) {
+            Log.e(TAG, "No SettingsState to launch Instant Hotspot settings");
+            return;
+        }
+        PendingIntent intent = state.getInstantTetherSettingsPendingIntent();
+        if (intent == null) {
+            Log.e(TAG, "No PendingIntent to launch Instant Hotspot settings");
+            return;
+        }
+        sendSettingsIntent(intent);
+    }
+
+    @WorkerThread
+    @VisibleForTesting
+    void sendSettingsIntent(@NonNull PendingIntent intent) {
+        try {
+            log("sendSettingsIntent(), sent intent:" + intent);
+            intent.send();
+        } catch (PendingIntent.CanceledException e) {
+            Log.e(TAG, "Failed to launch Instant Hotspot settings", e);
+        }
+    }
+
+    @WorkerThread
+    class ClientCallback implements SharedConnectivityClientCallback {
+
+        @Override
+        public void onHotspotNetworkConnectionStatusChanged(HotspotNetworkConnectionStatus status) {
+            log("onHotspotNetworkConnectionStatusChanged(), status:" + status);
+        }
+
+        @Override
+        public void onHotspotNetworksUpdated(List<HotspotNetwork> networks) {
+            log("onHotspotNetworksUpdated(), networks:" + networks);
+        }
+
+        @Override
+        public void onKnownNetworkConnectionStatusChanged(KnownNetworkConnectionStatus status) {
+            log("onKnownNetworkConnectionStatusChanged(), status:" + status);
+        }
+
+        @Override
+        public void onKnownNetworksUpdated(List<KnownNetwork> networks) {
+            log("onKnownNetworksUpdated(), networks:" + networks);
+        }
+
+        @Override
+        public void onRegisterCallbackFailed(Exception e) {
+            Log.e(TAG, "onRegisterCallbackFailed(), e:" + e);
+        }
+
+        @Override
+        public void onServiceConnected() {
+            SharedConnectivitySettingsState state = mManager.getSettingsState();
+            Log.d(TAG, "onServiceConnected(), Manager#getSettingsState:" + state);
+            mSettingsState.postValue(state);
+        }
+
+        @Override
+        public void onServiceDisconnected() {
+            log("onServiceDisconnected()");
+        }
+
+        @Override
+        public void onSharedConnectivitySettingsChanged(SharedConnectivitySettingsState state) {
+            Log.d(TAG, "onSharedConnectivitySettingsChanged(), state:" + state);
+            mSettingsState.postValue(state);
+        }
+    }
+
+    private void log(String msg) {
+        FeatureFactory.getFactory(mAppContext).getWifiFeatureProvider().verboseLog(TAG, msg);
+    }
+}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
index d8c3908..fa897b7 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
@@ -76,6 +76,8 @@
     static final String KEY_WIFI_HOTSPOT_SECURITY = "wifi_hotspot_security";
     @VisibleForTesting
     static final String KEY_WIFI_HOTSPOT_SPEED = "wifi_hotspot_speed";
+    @VisibleForTesting
+    static final String KEY_INSTANT_HOTSPOT = "wifi_hotspot_instant";
 
     @VisibleForTesting
     SettingsMainSwitchBar mMainSwitchBar;
@@ -103,6 +105,8 @@
     Preference mWifiHotspotSecurity;
     @VisibleForTesting
     Preference mWifiHotspotSpeed;
+    @VisibleForTesting
+    Preference mInstantHotspot;
 
     static {
         TETHER_STATE_CHANGE_FILTER = new IntentFilter(WIFI_AP_STATE_CHANGED_ACTION);
@@ -148,6 +152,7 @@
                 .getWifiTetherViewModel(this);
         if (mWifiTetherViewModel != null) {
             setupSpeedFeature(mWifiTetherViewModel.isSpeedFeatureAvailable());
+            setupInstantHotspot(mWifiTetherViewModel.isInstantHotspotFeatureAvailable());
             mWifiTetherViewModel.getRestarting().observe(this, this::onRestartingChanged);
         }
     }
@@ -167,6 +172,24 @@
         }
     }
 
+    @VisibleForTesting
+    void setupInstantHotspot(boolean isFeatureAvailable) {
+        if (!isFeatureAvailable) {
+            return;
+        }
+        mInstantHotspot = findPreference(KEY_INSTANT_HOTSPOT);
+        if (mInstantHotspot == null) {
+            Log.e(TAG, "Failed to find Instant Hotspot preference:" + KEY_INSTANT_HOTSPOT);
+            return;
+        }
+        mWifiTetherViewModel.getInstantHotspotSummary()
+                .observe(this, this::onInstantHotspotChanged);
+        mInstantHotspot.setOnPreferenceClickListener(p -> {
+            mWifiTetherViewModel.launchInstantHotspotSettings();
+            return true;
+        });
+    }
+
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
@@ -279,6 +302,16 @@
     }
 
     @VisibleForTesting
+    void onInstantHotspotChanged(String summary) {
+        if (summary == null) {
+            mInstantHotspot.setVisible(false);
+            return;
+        }
+        mInstantHotspot.setVisible(true);
+        mInstantHotspot.setSummary(summary);
+    }
+
+    @VisibleForTesting
     SoftApConfiguration buildNewConfig() {
         SoftApConfiguration currentConfig = mWifiTetherViewModel.getSoftApConfiguration();
         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(currentConfig);
diff --git a/src/com/android/settings/wifi/tether/WifiTetherViewModel.java b/src/com/android/settings/wifi/tether/WifiTetherViewModel.java
index fb2160f..b0a18a8 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherViewModel.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherViewModel.java
@@ -28,7 +28,9 @@
 
 import android.app.Application;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.AndroidViewModel;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
@@ -36,6 +38,8 @@
 
 import com.android.settings.R;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.wifi.factory.WifiFeatureProvider;
+import com.android.settings.wifi.repository.SharedConnectivityRepository;
 import com.android.settings.wifi.repository.WifiHotspotRepository;
 
 import org.jetbrains.annotations.NotNull;
@@ -48,6 +52,8 @@
  */
 public class WifiTetherViewModel extends AndroidViewModel {
     private static final String TAG = "WifiTetherViewModel";
+    static final int RES_INSTANT_HOTSPOT_SUMMARY_ON = R.string.wifi_hotspot_instant_summary_on;
+    static final int RES_INSTANT_HOTSPOT_SUMMARY_OFF = R.string.wifi_hotspot_instant_summary_off;
 
     static Map<Integer, Integer> sSecuritySummaryResMap = new HashMap<>();
 
@@ -75,10 +81,23 @@
     protected final Observer<Integer> mSecurityTypeObserver = st -> onSecurityTypeChanged(st);
     protected final Observer<Integer> mSpeedTypeObserver = st -> onSpeedTypeChanged(st);
 
+    private SharedConnectivityRepository mSharedConnectivityRepository;
+    @VisibleForTesting
+    MutableLiveData<String> mInstantHotspotSummary = new MutableLiveData<>();
+    @VisibleForTesting
+    Observer<SharedConnectivitySettingsState> mInstantHotspotStateObserver =
+            state -> onInstantHotspotStateChanged(state);
+
     public WifiTetherViewModel(@NotNull Application application) {
         super(application);
-        mWifiHotspotRepository = FeatureFactory.getFactory(application).getWifiFeatureProvider()
-                .getWifiHotspotRepository();
+        WifiFeatureProvider featureProvider = FeatureFactory.getFactory(application)
+                .getWifiFeatureProvider();
+        mWifiHotspotRepository = featureProvider.getWifiHotspotRepository();
+        mSharedConnectivityRepository = featureProvider.getSharedConnectivityRepository();
+        if (mSharedConnectivityRepository.isServiceAvailable()) {
+            mSharedConnectivityRepository.getSettingsState()
+                    .observeForever(mInstantHotspotStateObserver);
+        }
     }
 
     @Override
@@ -89,6 +108,10 @@
         if (mSpeedSummary != null) {
             mWifiHotspotRepository.getSpeedType().removeObserver(mSpeedTypeObserver);
         }
+        if (mSharedConnectivityRepository.isServiceAvailable()) {
+            mSharedConnectivityRepository.getSettingsState()
+                    .removeObserver(mInstantHotspotStateObserver);
+        }
     }
 
     /**
@@ -169,4 +192,47 @@
     public LiveData<Boolean> getRestarting() {
         return mWifiHotspotRepository.getRestarting();
     }
+
+    /**
+     * Return whether Wi-Fi Instant Hotspot feature is available or not.
+     *
+     * @return {@code true} if Wi-Fi Instant Hotspot feature is available
+     */
+    public boolean isInstantHotspotFeatureAvailable() {
+        return mSharedConnectivityRepository.isServiceAvailable();
+    }
+
+    /**
+     * Gets InstantHotspotSummary
+     */
+    public LiveData<String> getInstantHotspotSummary() {
+        return mInstantHotspotSummary;
+    }
+
+    @VisibleForTesting
+    void onInstantHotspotStateChanged(SharedConnectivitySettingsState state) {
+        log("onInstantHotspotStateChanged(), state:" + state);
+        if (state == null) {
+            mInstantHotspotSummary.setValue(null);
+            return;
+        }
+        mInstantHotspotSummary.setValue(getInstantHotspotSummary(state.isInstantTetherEnabled()));
+    }
+
+    private String getInstantHotspotSummary(boolean enabled) {
+        return getApplication().getString(
+                enabled ? RES_INSTANT_HOTSPOT_SUMMARY_ON : RES_INSTANT_HOTSPOT_SUMMARY_OFF);
+    }
+
+    /**
+     * Launch Instant Hotspot Settings
+     */
+    public void launchInstantHotspotSettings() {
+        mSharedConnectivityRepository.launchSettings();
+    }
+
+    private void log(String msg) {
+        FeatureFactory.getFactory(getApplication().getApplicationContext()).getWifiFeatureProvider()
+                .verboseLog(TAG, msg);
+    }
 }
diff --git a/tests/robotests/assets/exempt_not_implementing_instrumentable b/tests/robotests/assets/exempt_not_implementing_instrumentable
index 04ef0ef..28e1e73 100644
--- a/tests/robotests/assets/exempt_not_implementing_instrumentable
+++ b/tests/robotests/assets/exempt_not_implementing_instrumentable
@@ -1,8 +1,7 @@
 com.android.settings.deletionhelper.ActivationWarningFragment
 com.android.settings.applications.appops.AppOpsCategory
 com.android.settings.CustomListPreference$CustomListPreferenceDialogFragment
-com.android.settings.password.ChooseLockPassword$SaveAndFinishWorker
-com.android.settings.password.ChooseLockPattern$SaveAndFinishWorker
+com.android.settings.password.SaveAndFinishWorker
 com.android.settings.RestrictedListPreference$RestrictedListPreferenceDialogFragment
 com.android.settings.password.ConfirmDeviceCredentialBaseFragment$LastTryDialog
 com.android.settings.password.CredentialCheckResultTracker
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index f0a18ec..733a5e6 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -16,9 +16,15 @@
 
 package com.android.settings;
 
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PASSWORD;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,6 +37,7 @@
 
 import android.app.ActionBar;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyResourcesManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -60,6 +67,7 @@
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.fragment.app.FragmentActivity;
 
+import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
 
 import org.junit.After;
@@ -94,6 +102,8 @@
     @Mock
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
+    private DevicePolicyResourcesManager mDevicePolicyResourcesManager;
+    @Mock
     private UserManager mMockUserManager;
     @Mock
     private PackageManager mPackageManager;
@@ -348,4 +358,103 @@
                 SecurityException.class,
                 () -> Utils.checkUserOwnsFrpCredential(mContext, 123));
     }
+
+    @Test
+    public void getConfirmCredentialStringForUser_Pin_shouldReturnCorrectString() {
+        setUpForConfirmCredentialString(false /* isEffectiveUserManagedProfile */);
+
+        when(mContext.getString(R.string.lockpassword_confirm_your_pin_generic))
+                .thenReturn("PIN");
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_PIN);
+
+        assertThat(confirmCredentialString).isEqualTo("PIN");
+    }
+
+    @Test
+    public void getConfirmCredentialStringForUser_Pattern_shouldReturnCorrectString() {
+        setUpForConfirmCredentialString(false /* isEffectiveUserManagedProfile */);
+
+        when(mContext.getString(R.string.lockpassword_confirm_your_pattern_generic))
+                .thenReturn("PATTERN");
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+
+        assertThat(confirmCredentialString).isEqualTo("PATTERN");
+    }
+
+    @Test
+    public void getConfirmCredentialStringForUser_Password_shouldReturnCorrectString() {
+        setUpForConfirmCredentialString(false /* isEffectiveUserManagedProfile */);
+
+        when(mContext.getString(R.string.lockpassword_confirm_your_password_generic))
+                .thenReturn("PASSWORD");
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+
+        assertThat(confirmCredentialString).isEqualTo("PASSWORD");
+    }
+
+    @Test
+    public void getConfirmCredentialStringForUser_workPin_shouldReturnCorrectString() {
+        setUpForConfirmCredentialString(true /* isEffectiveUserManagedProfile */);
+
+        when(mDevicePolicyResourcesManager
+                .getString(eq(WORK_PROFILE_CONFIRM_PIN), any()))
+                .thenReturn("WORK PIN");
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_PIN);
+
+        assertThat(confirmCredentialString).isEqualTo("WORK PIN");
+    }
+
+    @Test
+    public void getConfirmCredentialStringForUser_workPattern_shouldReturnCorrectString() {
+        setUpForConfirmCredentialString(true /* isEffectiveUserManagedProfile */);
+
+        when(mDevicePolicyResourcesManager
+                .getString(eq(WORK_PROFILE_CONFIRM_PATTERN), any()))
+                .thenReturn("WORK PATTERN");
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
+
+        assertThat(confirmCredentialString).isEqualTo("WORK PATTERN");
+    }
+
+    @Test
+    public void getConfirmCredentialStringForUser_workPassword_shouldReturnCorrectString() {
+        setUpForConfirmCredentialString(true /* isEffectiveUserManagedProfile */);
+
+        when(mDevicePolicyResourcesManager
+                .getString(eq(WORK_PROFILE_CONFIRM_PASSWORD), any()))
+                .thenReturn("WORK PASSWORD");
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+
+        assertThat(confirmCredentialString).isEqualTo("WORK PASSWORD");
+    }
+
+    @Test
+    public void getConfirmCredentialStringForUser_credentialTypeNone_shouldReturnNull() {
+        setUpForConfirmCredentialString(false /* isEffectiveUserManagedProfile */);
+
+        String confirmCredentialString = Utils.getConfirmCredentialStringForUser(mContext,
+                USER_ID, LockPatternUtils.CREDENTIAL_TYPE_NONE);
+
+        assertNull(confirmCredentialString);
+    }
+
+    private void setUpForConfirmCredentialString(boolean isEffectiveUserManagedProfile) {
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        when(mMockUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(USER_ID);
+        when(mMockUserManager.isManagedProfile(USER_ID)).thenReturn(isEffectiveUserManagedProfile);
+        when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDevicePolicyManager);
+        when(mDevicePolicyManager.getResources()).thenReturn(mDevicePolicyResourcesManager);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java
index e14e271..ea2852f 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardTest.java
@@ -38,6 +38,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
@@ -93,6 +94,7 @@
         when(mAccessibilityManager.getInstalledAccessibilityServiceList()).thenReturn(
                 mAccessibilityServices);
         doReturn(mActivity).when(mFragment).getActivity();
+        doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
         doReturn(mFooterBarMixin).when(mGlifLayoutView).getMixin(FooterBarMixin.class);
     }
 
diff --git a/tests/robotests/src/com/android/settings/accessibility/AvailableHearingDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/accessibility/AvailableHearingDeviceUpdaterTest.java
index 6305014..0aab5bb 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AvailableHearingDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AvailableHearingDeviceUpdaterTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothDevice;
@@ -80,8 +79,9 @@
     @Test
     public void isFilterMatch_connectedHearingDevice_returnTrue() {
         CachedBluetoothDevice connectedHearingDevice = mCachedBluetoothDevice;
-        when(connectedHearingDevice.isConnectedHearingAidDevice()).thenReturn(true);
-        doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState();
+        when(connectedHearingDevice.isHearingAidDevice()).thenReturn(true);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
                 new ArrayList<>(List.of(connectedHearingDevice)));
 
@@ -91,8 +91,9 @@
     @Test
     public void isFilterMatch_nonConnectedHearingDevice_returnFalse() {
         CachedBluetoothDevice nonConnectedHearingDevice = mCachedBluetoothDevice;
-        when(nonConnectedHearingDevice.isConnectedHearingAidDevice()).thenReturn(false);
-        doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState();
+        when(nonConnectedHearingDevice.isHearingAidDevice()).thenReturn(true);
+        when(mBluetoothDevice.isConnected()).thenReturn(false);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
                 new ArrayList<>(List.of(nonConnectedHearingDevice)));
 
@@ -103,7 +104,8 @@
     public void isFilterMatch_connectedBondingHearingDevice_returnFalse() {
         CachedBluetoothDevice connectedBondingHearingDevice = mCachedBluetoothDevice;
         when(connectedBondingHearingDevice.isHearingAidDevice()).thenReturn(true);
-        doReturn(BluetoothDevice.BOND_BONDING).when(mBluetoothDevice).getBondState();
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
                 new ArrayList<>(List.of(connectedBondingHearingDevice)));
 
@@ -114,8 +116,8 @@
     public void isFilterMatch_hearingDeviceNotInCachedDevicesList_returnFalse() {
         CachedBluetoothDevice notInCachedDevicesListDevice = mCachedBluetoothDevice;
         when(notInCachedDevicesListDevice.isHearingAidDevice()).thenReturn(true);
-        doReturn(BluetoothDevice.BOND_BONDED).when(mBluetoothDevice).getBondState();
-        doReturn(false).when(mBluetoothDevice).isConnected();
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(new ArrayList<>());
 
         assertThat(mUpdater.isFilterMatched(notInCachedDevicesListDevice)).isEqualTo(false);
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java
index 194b766..3889cf0 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java
@@ -95,8 +95,7 @@
     }
 
     @Test
-    public void isHearingAidSupported_supported_returnTrue() {
-        mBluetoothAdapter.enable();
+    public void isHearingAidSupported_ashaSupported_returnTrue() {
         mShadowBluetoothAdapter.clearSupportedProfiles();
         mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
 
@@ -104,15 +103,20 @@
     }
 
     @Test
-    public void isHearingAidSupported_bluetoothOff_returnFalse() {
+    public void isHearingAidSupported_hapSupported_returnTrue() {
         mShadowBluetoothAdapter.clearSupportedProfiles();
-        mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
-        mBluetoothAdapter.disable();
+        mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HAP_CLIENT);
+
+        assertThat(mHelper.isHearingAidSupported()).isTrue();
+    }
+
+    @Test
+    public void isHearingAidSupported_unsupported_returnFalse() {
+        mShadowBluetoothAdapter.clearSupportedProfiles();
 
         assertThat(mHelper.isHearingAidSupported()).isFalse();
     }
 
-
     @Test
     public void isAllHearingAidRelatedProfilesReady_allReady_returnTrue() {
         when(mHearingAidProfile.isProfileReady()).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
index 09db6e9..56ab082 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
@@ -37,8 +37,10 @@
 import com.android.settings.utils.ActivityControllerWrapper;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.CsipSetCoordinatorProfile;
 import com.android.settingslib.bluetooth.HearingAidInfo;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -52,6 +54,9 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link HearingAidUtils}. */
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class,
@@ -72,6 +77,8 @@
     private LocalBluetoothManager mLocalBluetoothManager;
     @Mock
     private CachedBluetoothDeviceManager mCachedDeviceManager;
+    @Mock
+    private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
     private BluetoothDevice mBluetoothDevice;
     private BluetoothAdapter mBluetoothAdapter;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
@@ -137,6 +144,38 @@
     }
 
     @Test
+    public void launchHearingAidPairingDialog_deviceSupportsCsip_csipEnabled_noDialog() {
+        when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
+        when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
+                HearingAidInfo.DeviceSide.SIDE_LEFT);
+        makeDeviceSupportCsip();
+        makeDeviceEnableCsip(true);
+
+        HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice);
+
+        final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+    }
+
+    @Test
+    public void launchHearingAidPairingDialog_deviceSupportsCsip_csipDisabled_dialogShown() {
+        when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
+                HearingAidInfo.DeviceMode.MODE_BINAURAL);
+        when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
+                HearingAidInfo.DeviceSide.SIDE_LEFT);
+        makeDeviceSupportCsip();
+        makeDeviceEnableCsip(false);
+
+        HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, mCachedBluetoothDevice);
+
+        final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog.isShowing()).isTrue();
+    }
+
+    @Test
     public void launchHearingAidPairingDialog_dialogShown() {
         when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
         when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
@@ -150,6 +189,17 @@
         assertThat(dialog.isShowing()).isTrue();
     }
 
+    private void makeDeviceSupportCsip() {
+        List<LocalBluetoothProfile> uuids = new ArrayList<>();
+        uuids.add(mCsipSetCoordinatorProfile);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(uuids);
+    }
+
+    private void makeDeviceEnableCsip(boolean enabled) {
+        when(mCsipSetCoordinatorProfile.isEnabled(mCachedBluetoothDevice.getDevice()))
+                .thenReturn(enabled);
+    }
+
     private void setupEnvironment() {
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java
index 1cd301f..4ee2a2d 100644
--- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreferenceFragmentForSetupWizardTest.java
@@ -22,6 +22,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -29,6 +30,7 @@
 import android.content.Context;
 
 import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
@@ -73,6 +75,7 @@
         final LayoutPreference resetPreference =
                 new LayoutPreference(mContext, R.layout.accessibility_text_reading_reset_button);
         doReturn(mContext).when(mFragment).getContext();
+        doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
         doReturn(resetPreference).when(mFragment).findPreference(RESET_KEY);
         doReturn(mFooterBarMixin).when(mGlifLayoutView).getMixin(FooterBarMixin.class);
     }
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest.java
index 84783b21..aa622f5 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -27,6 +28,7 @@
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
@@ -75,6 +77,7 @@
         mFragment =
                 spy(new TestToggleScreenMagnificationPreferenceFragmentForSetupWizard(mContext));
         doReturn(mActivity).when(mFragment).getActivity();
+        doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
         when(mActivity.getSwitchBar()).thenReturn(mSwitchBar);
         doReturn(mFooterBarMixin).when(mGlifLayoutView).getMixin(FooterBarMixin.class);
     }
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizardTest.java
index c604652..77e5b1f 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizardTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizardTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -28,6 +29,7 @@
 import android.content.Context;
 import android.os.Bundle;
 
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
 import androidx.test.core.app.ApplicationProvider;
@@ -72,6 +74,7 @@
     public void setUp() {
         mFragment = spy(new TestToggleScreenReaderPreferenceFragmentForSetupWizard(mContext));
         doReturn(mActivity).when(mFragment).getActivity();
+        doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
         when(mActivity.getSwitchBar()).thenReturn(mSwitchBar);
         doReturn(mFooterBarMixin).when(mGlifLayoutView).getMixin(FooterBarMixin.class);
     }
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizardTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizardTest.java
index 7893831..8878064 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizardTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizardTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -28,6 +29,7 @@
 import android.content.Context;
 import android.os.Bundle;
 
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
 import androidx.test.core.app.ApplicationProvider;
@@ -72,6 +74,7 @@
     public void setUp() {
         mFragment = spy(new TestToggleSelectToSpeakPreferenceFragmentForSetupWizard(mContext));
         doReturn(mActivity).when(mFragment).getActivity();
+        doReturn(mock(LifecycleOwner.class)).when(mFragment).getViewLifecycleOwner();
         when(mActivity.getSwitchBar()).thenReturn(mSwitchBar);
         doReturn(mFooterBarMixin).when(mGlifLayoutView).getMixin(FooterBarMixin.class);
     }
diff --git a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java
deleted file mode 100644
index da5ada7..0000000
--- a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2017 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.applications;
-
-import static com.android.settings.core.BasePreferenceController.AVAILABLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ModuleInfo;
-import android.content.pm.PackageManager;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.datausage.AppStateDataUsageBridge;
-import com.android.settings.testutils.shadow.ShadowApplicationsState;
-import com.android.settings.testutils.shadow.ShadowUserManager;
-import com.android.settingslib.applications.ApplicationsState;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-import java.util.ArrayList;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowUserManager.class, ShadowApplicationsState.class})
-public class SpecialAppAccessPreferenceControllerTest {
-
-    private Context mContext;
-    @Mock
-    private ApplicationsState.Session mSession;
-    @Mock
-    private PreferenceScreen mScreen;
-    @Mock
-    private PackageManager mPackageManager;
-
-    private SpecialAppAccessPreferenceController mController;
-    private Preference mPreference;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application);
-        when(mContext.getApplicationContext()).thenReturn(mContext);
-        ShadowUserManager.getShadow().setProfileIdsWithDisabled(new int[]{0});
-        doReturn(mPackageManager).when(mContext).getPackageManager();
-        doReturn(new ArrayList<ModuleInfo>()).when(mPackageManager).getInstalledModules(anyInt());
-        mController = new SpecialAppAccessPreferenceController(mContext, "test_key");
-        mPreference = new Preference(mContext);
-        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
-
-        mController.mSession = mSession;
-    }
-
-    @Test
-    public void getAvailabilityState_unsearchable() {
-        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
-    }
-
-    @Test
-    public void updateState_shouldSetSummary() {
-        final ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
-        final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
-        entry.hasLauncherEntry = true;
-        entry.info = new ApplicationInfo();
-        entry.extraInfo = new AppStateDataUsageBridge.DataUsageState(
-                true /* allowlisted */, false /* denylisted */);
-        apps.add(entry);
-        when(mSession.getAllApps()).thenReturn(apps);
-
-        mController.displayPreference(mScreen);
-        mController.onExtraInfoUpdated();
-
-        assertThat(mPreference.getSummary())
-                .isEqualTo(mContext.getResources().getQuantityString(
-                        R.plurals.special_access_summary, 1, 1));
-    }
-
-    @Test
-    public void updateState_wrongExtraInfo_shouldNotIncludeInSummary() {
-        final ArrayList<ApplicationsState.AppEntry> apps = new ArrayList<>();
-        final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class);
-        entry.hasLauncherEntry = true;
-        entry.info = new ApplicationInfo();
-        entry.extraInfo = new AppStateNotificationBridge.NotificationsSentState();
-        apps.add(entry);
-        when(mSession.getAllApps()).thenReturn(apps);
-
-        mController.displayPreference(mScreen);
-        mController.onExtraInfoUpdated();
-
-        assertThat(mPreference.getSummary())
-                .isEqualTo(mContext.getResources().getQuantityString(
-                        R.plurals.special_access_summary, 0, 0));
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
new file mode 100644
index 0000000..d98b0e7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appcompat;
+
+import static com.android.settings.applications.appcompat.UserAspectRatioDetails.KEY_PREF_3_2;
+import static com.android.settings.applications.appcompat.UserAspectRatioDetails.KEY_PREF_DEFAULT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.shadow.ShadowActivityManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * To run test: atest SettingsRoboTests:UserAspectRatioDetailsTest
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowActivityManager.class})
+public class UserAspectRatioDetailsTest {
+
+    @Mock
+    private UserAspectRatioManager mUserAspectRatioManager;
+    @Mock
+    private IActivityManager mAm;
+
+    private RadioWithImagePreference mRadioButtonPref;
+    private Context mContext;
+    private UserAspectRatioDetails mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mFragment = spy(new UserAspectRatioDetails());
+        when(mFragment.getContext()).thenReturn(mContext);
+        when(mFragment.getAspectRatioManager()).thenReturn(mUserAspectRatioManager);
+        ShadowActivityManager.setService(mAm);
+        mRadioButtonPref = new RadioWithImagePreference(mContext);
+    }
+
+    @Test
+    public void onRadioButtonClicked_prefChange_shouldStopActivity() throws RemoteException {
+        // Default was already selected
+        mRadioButtonPref.setKey(KEY_PREF_DEFAULT);
+        mFragment.onRadioButtonClicked(mRadioButtonPref);
+        // Preference changed
+        mRadioButtonPref.setKey(KEY_PREF_3_2);
+        mFragment.onRadioButtonClicked(mRadioButtonPref);
+        // Only triggered once when preference change
+        verify(mAm).stopAppForUser(any(), anyInt());
+    }
+
+    @Test
+    public void onRadioButtonClicked_prefChange_shouldSetAspectRatio() throws RemoteException {
+        // Default was already selected
+        mRadioButtonPref.setKey(KEY_PREF_DEFAULT);
+        mFragment.onRadioButtonClicked(mRadioButtonPref);
+        // Preference changed
+        mRadioButtonPref.setKey(KEY_PREF_3_2);
+        mFragment.onRadioButtonClicked(mRadioButtonPref);
+        // Only triggered once when preference changes
+        verify(mUserAspectRatioManager).setUserMinAspectRatio(
+                any(), anyInt(), anyInt());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java
deleted file mode 100644
index f039c97..0000000
--- a/tests/robotests/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 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.applications.specialaccess;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-
-import com.android.settings.R;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.class)
-public class DataSaverControllerTest {
-
-    private Context mContext;
-    private Resources mResources;
-    private DataSaverController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application.getApplicationContext());
-
-        mResources = spy(mContext.getResources());
-        when(mContext.getResources()).thenReturn(mResources);
-
-        mController = new DataSaverController(mContext, "key");
-    }
-
-    @Test
-    public void testDataSaver_byDefault_shouldBeShown() {
-        when(mResources.getBoolean(R.bool.config_show_data_saver)).thenReturn(true);
-        assertThat(mController.isAvailable()).isTrue();
-    }
-
-    @Ignore
-    @Test
-    @Config(qualifiers = "mcc999")
-    public void testDataSaver_ifDisabledByCarrier_shouldNotBeShown() {
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
-    public void testDataSaver_ifDisabled_shouldNotBeShown() {
-        when(mResources.getBoolean(R.bool.config_show_data_saver)).thenReturn(false);
-        assertThat(mController.isAvailable()).isFalse();
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
index 2ce0757..4781f56 100644
--- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
@@ -43,6 +43,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
+import android.util.AndroidRuntimeException;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -58,7 +59,10 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
+import com.android.settings.biometrics.BiometricStatusPreferenceController;
 import com.android.settings.biometrics.BiometricsSplitScreenDialog;
+import com.android.settings.biometrics.face.FaceStatusPreferenceController;
+import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController;
 import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.ShadowFragment;
@@ -68,7 +72,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -86,7 +89,6 @@
 import java.util.List;
 import java.util.Map;
 
-@Ignore
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class})
 public class CombinedBiometricProfileSettingsTest {
@@ -104,6 +106,10 @@
     @Mock
     private BiometricSettingsAppPreferenceController mBiometricSettingsAppPreferenceController;
     @Mock
+    private FingerprintStatusPreferenceController mFingerprintStatusPreferenceController;
+    @Mock
+    private FaceStatusPreferenceController mFaceStatusPreferenceController;
+    @Mock
     private FaceManager mFaceManager;
     @Mock
     private FragmentTransaction mFragmentTransaction;
@@ -128,6 +134,29 @@
         List<AbstractPreferenceController> controllerList = new ArrayList<>();
         controllerList.add(mBiometricSettingsAppPreferenceController);
         preferenceControllers.put(BiometricSettingsAppPreferenceController.class, controllerList);
+        controllerList.add(mFingerprintStatusPreferenceController);
+        preferenceControllers.put(FingerprintStatusPreferenceController.class, controllerList);
+        controllerList.add(mFaceStatusPreferenceController);
+        preferenceControllers.put(FaceStatusPreferenceController.class, controllerList);
+
+        doAnswer(invocation -> {
+            final Preference preference = invocation.getArgument(0);
+            return preference.getKey().equals(mFragment.getFingerprintPreferenceKey());
+        }).when(mFingerprintStatusPreferenceController)
+                .setPreferenceTreeClickLauncher(any(), any());
+        doAnswer(invocation -> {
+            final Preference preference = invocation.getArgument(0);
+            return preference.getKey().equals(mFragment.getFingerprintPreferenceKey());
+        }).when(mFingerprintStatusPreferenceController).handlePreferenceTreeClick(any());
+        doAnswer(invocation -> {
+            final Preference preference = invocation.getArgument(0);
+            return preference.getKey().equals(mFragment.getFacePreferenceKey());
+        }).when(mFaceStatusPreferenceController)
+                .setPreferenceTreeClickLauncher(any(), any());
+        doAnswer(invocation -> {
+            final Preference preference = invocation.getArgument(0);
+            return preference.getKey().equals(mFragment.getFacePreferenceKey());
+        }).when(mFaceStatusPreferenceController).handlePreferenceTreeClick(any());
 
         doAnswer(invocation -> {
             final CharSequence key = invocation.getArgument(0);
@@ -164,7 +193,7 @@
         preference.setKey(mFragment.getFingerprintPreferenceKey());
         mFragment.onPreferenceTreeClick(preference);
 
-        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
+        verify(mFingerprintStatusPreferenceController).handlePreferenceTreeClick(
                 mPreferenceCaptor.capture());
         List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
 
@@ -224,7 +253,7 @@
         mFragment.onActivityResult(CONFIRM_REQUEST, RESULT_FINISHED,
                 new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L));
 
-        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
+        verify(mFingerprintStatusPreferenceController).handlePreferenceTreeClick(
                 mPreferenceCaptor.capture());
         List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
         assertThat(capturedPreferences.size()).isEqualTo(1);
@@ -254,7 +283,7 @@
         preference.setKey(mFragment.getFacePreferenceKey());
         mFragment.onPreferenceTreeClick(preference);
 
-        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
+        verify(mFaceStatusPreferenceController).handlePreferenceTreeClick(
                 mPreferenceCaptor.capture());
         List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
         assertThat(capturedPreferences.size()).isEqualTo(1);
@@ -313,7 +342,7 @@
         preference.setKey(mFragment.getFacePreferenceKey());
         mFragment.onPreferenceTreeClick(preference);
 
-        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
+        verify(mFaceStatusPreferenceController).handlePreferenceTreeClick(
                 mPreferenceCaptor.capture());
         List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
         assertThat(capturedPreferences.size()).isEqualTo(1);
@@ -323,7 +352,7 @@
     @Test
     public void testClickFingerprintUnlock_inMultiWindow_withoutEnrolledFp_showsDialog() {
         testClickFingerprintUnlock(true /* isInMultiWindow */, false /* hasEnrolledFingerprint */);
-        verifyShowsDialogAfterClickingUnlock();
+        verifyShowsDialogAfterClickingUnlock(mFragment.getFingerprintPreferenceKey());
     }
 
     @Test
@@ -380,7 +409,7 @@
     @Test
     public void testClickFaceUnlock_inMultiWindow_withoutEnrolledFp_showsDialog() {
         testClickFaceUnlock(true /* isInMultiWindow */, false /*hasEnrolledFace*/);
-        verifyShowsDialogAfterClickingUnlock();
+        verifyShowsDialogAfterClickingUnlock(mFragment.getFacePreferenceKey());
     }
 
     @Test
@@ -424,8 +453,11 @@
     }
 
     private void verifyNoDialogAfterClickingUnlock(String preferenceKey) {
-        verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick(
-                mPreferenceCaptor.capture());
+        final BiometricStatusPreferenceController controller =
+                preferenceKey.equals(mFragment.getFacePreferenceKey())
+                        ? mFaceStatusPreferenceController
+                        : mFingerprintStatusPreferenceController;
+        verify(controller).handlePreferenceTreeClick(mPreferenceCaptor.capture());
         List<Preference> capturedPreferences = mPreferenceCaptor.getAllValues();
         assertThat(capturedPreferences).hasSize(1);
         assertThat(capturedPreferences.get(0).getKey()).isEqualTo(preferenceKey);
@@ -433,12 +465,77 @@
                 eq(BiometricsSplitScreenDialog.class.getName()));
     }
 
-    private void verifyShowsDialogAfterClickingUnlock() {
-        verify(mBiometricSettingsAppPreferenceController, never()).handlePreferenceTreeClick(any());
+    private void verifyShowsDialogAfterClickingUnlock(String preferenceKey) {
+        final BiometricStatusPreferenceController controller =
+                preferenceKey.equals(mFragment.getFacePreferenceKey())
+                        ? mFaceStatusPreferenceController
+                        : mFingerprintStatusPreferenceController;
+        verify(controller, never()).handlePreferenceTreeClick(any());
         verify(mFragmentTransaction).add(any(),
                 eq(BiometricsSplitScreenDialog.class.getName()));
     }
 
+    @Test
+    public void testNoCrashIfDetachActivityDuringGeneratingChallengeThroughFaceManager() {
+        doAnswer(invocation -> {
+            final FaceManager.GenerateChallengeCallback callback =
+                    invocation.getArgument(1);
+            mFragment.onPause();
+            mFragment.onStop();
+            mFragment.onDestroy();
+            mFragment.onDetach();
+            doReturn(null).when(mFragment).getActivity();
+            callback.onGenerateChallengeResult(0, 0, 1L);
+            return null;
+        }).when(mFaceManager).generateChallenge(anyInt(), any());
+        doThrow(new IllegalStateException("Test")).when(mFragment).requestGatekeeperHat(
+                any(), anyLong(), anyInt(), anyLong());
+        FragmentManager fragmentManager = mock(FragmentManager.class);
+
+        // Start fragment
+        mFragment.onAttach(mContext);
+        mFragment.onCreate(null);
+        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
+        mFragment.onResume();
+
+        // User clicks on "Face Unlock"
+        final Preference preference = new Preference(mContext);
+        preference.setKey(mFragment.getFacePreferenceKey());
+        mFragment.onPreferenceTreeClick(preference);
+
+        verify(mFragment, never()).launchChooseOrConfirmLock();
+    }
+
+    @Test
+    public void testNoCrashIfDetachActivityDuringGeneratingChallengeThroughFingerprintManager() {
+        doAnswer(invocation -> {
+            final FingerprintManager.GenerateChallengeCallback callback =
+                    invocation.getArgument(1);
+            mFragment.onPause();
+            mFragment.onStop();
+            mFragment.onDestroy();
+            mFragment.onDetach();
+            doReturn(null).when(mFragment).getActivity();
+            callback.onChallengeGenerated(0, 0, 1L);
+            return null;
+        }).when(mFingerprintManager).generateChallenge(anyInt(), any());
+        doThrow(new IllegalStateException("Test")).when(mFragment).requestGatekeeperHat(
+                any(), anyLong(), anyInt(), anyLong());
+
+        // Start fragment
+        mFragment.onAttach(mContext);
+        mFragment.onCreate(null);
+        mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY);
+        mFragment.onResume();
+
+        // User clicks on "Fingerprint Unlock"
+        final Preference preference = new Preference(mContext);
+        preference.setKey(mFragment.getFingerprintPreferenceKey());
+        mFragment.onPreferenceTreeClick(preference);
+
+        verify(mFragment, never()).launchChooseOrConfirmLock();
+    }
+
     /**
      * a test fragment that initializes PreferenceScreen for testing.
      */
@@ -492,7 +589,9 @@
 
         @Override
         protected void launchChooseOrConfirmLock() {
-            // do nothing
+            if (getActivity() == null) {
+                throw new AndroidRuntimeException("TestFailed");
+            }
         }
     }
 }
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java
index c4da133..df15e5c 100644
--- a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollIntroductionTest.java
@@ -40,6 +40,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
@@ -116,6 +117,7 @@
     private FaceEnrollIntroduction mSpyActivity;
     private FakeFeatureFactory mFakeFeatureFactory;
     private ShadowUserManager mUserManager;
+    private Resources mResources;
 
     enum GateKeeperAction {CALL_SUPER, RETURN_BYTE_ARRAY, THROW_CREDENTIAL_NOT_MATCH}
 
@@ -245,6 +247,14 @@
         when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(faces);
     }
 
+    private void setFaceManagerToHaveWithUserId(int numEnrollments, int userId) {
+        List<Face> faces = new ArrayList<>();
+        for (int i = 0; i < numEnrollments; i++) {
+            faces.add(new Face("Face " + i /* name */, 1 /*faceId */, 1 /* deviceId */));
+        }
+        when(mFaceManager.getEnrolledFaces(userId)).thenReturn(faces);
+    }
+
     @Test
     public void intro_CheckCanEnroll() {
         setFaceManagerToHave(0 /* numEnrollments */);
@@ -546,4 +556,40 @@
         assertThat(mActivity.getPostureCallback()).isNull();
     }
 
+    @Test
+    public void testFaceEnrollIntroduction_maxFacesNotEnrolled_addUserProfile() {
+        // Enroll a face for one user
+        setFaceManagerToHaveWithUserId(1, 0);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mResources = spy(mContext.getResources());
+        when(mResources.getInteger(R.integer.suw_max_faces_enrollable)).thenReturn(1);
+
+        mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, new Intent());
+        mActivity = (TestFaceEnrollIntroduction) mController.get();
+
+        mController.create();
+
+        // The maximum number of faces is already enrolled
+        int result = mActivity.checkMaxEnrolled();
+        assertThat(result).isEqualTo(R.string.face_intro_error_max);
+
+        // Add another user profile
+        mUserManager.addUser(10, "", 0);
+        final Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_USER_ID, 10);
+
+        when(mResources.getInteger(R.integer.suw_max_faces_enrollable)).thenReturn(2);
+
+        mController = Robolectric.buildActivity(TestFaceEnrollIntroduction.class, intent);
+        mActivity = (TestFaceEnrollIntroduction) mController.get();
+
+        mController.create();
+
+        // The maximum number of faces hasn't been enrolled, so a new face
+        // can be enrolled for the added user profile
+        result = mActivity.checkMaxEnrolled();
+        assertThat(result).isEqualTo(0);
+    }
+
 }
diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceControllerTest.java
new file mode 100644
index 0000000..5f56fa7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceControllerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.face;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.biometrics.face.FaceSettingsRemoveButtonPreferenceController.ConfirmRemoveDialog;
+import com.android.settings.testutils.shadow.ShadowUserManager;
+import com.android.settingslib.widget.LayoutPreference;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowUserManager.class})
+public class FaceSettingsRemoveButtonPreferenceControllerTest {
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final String TEST_PREF_KEY = "baz";
+
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private PackageManager mPackageManager;
+    private SettingsActivity mActivity;
+    private Context mContext;
+    private FaceSettingsRemoveButtonPreferenceController mController;
+    private LayoutPreference mPreference;
+
+    @Before
+    public void setUp() {
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        ShadowApplication.getInstance().setSystemService(Context.FACE_SERVICE, mFaceManager);
+
+        mPreference = new LayoutPreference(mContext, R.layout.face_remove_button);
+        mController = new FaceSettingsRemoveButtonPreferenceController(mContext, TEST_PREF_KEY);
+
+        mActivity = spy(Robolectric.buildActivity(SettingsActivity.class).create().get());
+        mController.setActivity(mActivity);
+    }
+
+    @Test
+    public void testRotationConfirmRemoveDialog() {
+        // mController calls onClick(), the dialog is created.
+        mController.updateState(mPreference);
+        assertThat(mController.mRemoving).isFalse();
+
+        mController.onClick(
+                mPreference.findViewById(R.id.security_settings_face_settings_remove_button));
+
+        ConfirmRemoveDialog removeDialog =
+                (ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
+                        .findFragmentByTag(ConfirmRemoveDialog.class.getName());
+        assertThat(removeDialog).isNotNull();
+        assertThat(mController.mRemoving).isTrue();
+
+
+        // Simulate rotation, a new controller mController2 is created and updateState() is called.
+        // Since the dialog hasn't been dismissed, so mController2.mRemoving should be true
+        FaceSettingsRemoveButtonPreferenceController controller2 =
+                new FaceSettingsRemoveButtonPreferenceController(mContext, TEST_PREF_KEY);
+        controller2.setActivity(mActivity);
+        assertThat(controller2.mRemoving).isFalse();
+        controller2.updateState(mPreference);
+        assertThat(controller2.mRemoving).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
index 0f12d1e..a23eded 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java
@@ -148,6 +148,23 @@
     }
 
     @Test
+    public void fingerprintUdfpsEnrollInitStage_afterOnEnrollmentHelp_shouldVibrate() {
+        initializeActivityFor(TYPE_UDFPS_OPTICAL);
+
+        assertThat(getLayout().getDescriptionText()).isNotEqualTo("");
+
+        mActivity.configureEnrollmentStage(0 /* lottie */);
+        mActivity.onEnrollmentHelp(1/* FINGERPRINT_ACQUIRED_PARTIAL */, mContext.getString(
+                com.android.internal.R.string.fingerprint_acquired_partial));
+
+        verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
+
+        mActivity.onEnrollmentProgressChange(1, 1);
+        verify(mVibrator).vibrate(anyInt(), anyString(), any(), anyString(), any());
+
+    }
+
+    @Test
     public void fingerprintUdfpsOverlayEnrollment_gainFocus_shouldNotCancel() {
         initializeActivityFor(TYPE_UDFPS_OPTICAL);
 
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java
index 69f10d6..3eba91c 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroductionTest.java
@@ -85,6 +85,7 @@
     private Context mContext;
 
     private TestFingerprintEnrollIntroduction mFingerprintEnrollIntroduction;
+    private ActivityController<TestFingerprintEnrollIntroduction> mController;
 
     private static final int MAX_ENROLLMENTS = 5;
     private static final byte[] EXPECTED_TOKEN = new byte[] { 10, 20, 30, 40 };
@@ -121,9 +122,8 @@
 
     void setupFingerprintEnrollIntroWith(@NonNull Intent intent) {
 
-        final ActivityController<TestFingerprintEnrollIntroduction> controller =
-                Robolectric.buildActivity(TestFingerprintEnrollIntroduction.class, intent);
-        mFingerprintEnrollIntroduction = controller.get();
+        mController = Robolectric.buildActivity(TestFingerprintEnrollIntroduction.class, intent);
+        mFingerprintEnrollIntroduction = mController.get();
         mFingerprintEnrollIntroduction.mMockedFingerprintManager = mFingerprintManager;
         mFingerprintEnrollIntroduction.mMockedGatekeeperPasswordProvider =
                 mGatekeeperPasswordProvider;
@@ -137,7 +137,7 @@
         when(mLockPatternUtils.getActivePasswordQuality(userId))
                 .thenReturn(PASSWORD_QUALITY_SOMETHING);
 
-        controller.create();
+        mController.create();
     }
 
     void setFingerprintManagerToHave(int numEnrollments) {
@@ -277,6 +277,18 @@
         }
     }
 
+    @Test
+    public void clickNext_onActivityResult_pause_shouldFinish() {
+        setupFingerprintEnrollIntroWith(newTokenOnlyIntent());
+        mController.resume();
+        mFingerprintEnrollIntroduction.clickNextBtn();
+        mController.pause().stop();
+        assertThat(mFingerprintEnrollIntroduction.shouldFinishWhenBackgrounded()).isEqualTo(false);
+
+        mController.resume().pause().stop();
+        assertThat(mFingerprintEnrollIntroduction.shouldFinishWhenBackgrounded()).isEqualTo(true);
+    }
+
     private Intent newTokenOnlyIntent() {
         return new Intent()
                 .putExtra(EXTRA_KEY_CHALLENGE_TOKEN, new byte[] { 1 });
@@ -362,5 +374,16 @@
         protected void getChallenge(GenerateChallengeCallback callback) {
             callback.onChallengeGenerated(mNewSensorId, mUserId, mNewChallenge);
         }
+
+        @Override
+        protected boolean shouldFinishWhenBackgrounded() {
+            return super.shouldFinishWhenBackgrounded();
+        }
+
+        //mock click next btn
+        public void clickNextBtn() {
+            super.onNextButtonClick(null);
+        }
+
     }
 }
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
index 18b05ad..8b70550 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
@@ -16,12 +16,14 @@
 
 package com.android.settings.biometrics.fingerprint;
 
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 
 import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment;
 import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.ADD_FINGERPRINT_REQUEST;
 import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.CHOOSE_LOCK_GENERIC_REQUEST;
 import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.KEY_FINGERPRINT_ADD;
+import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.KEY_REQUIRE_SCREEN_ON_TO_AUTH;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -39,11 +41,16 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.UserInfo;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
@@ -61,6 +68,7 @@
 import com.android.settings.testutils.shadow.ShadowSettingsPreferenceFragment;
 import com.android.settings.testutils.shadow.ShadowUserManager;
 import com.android.settings.testutils.shadow.ShadowUtils;
+import com.android.settingslib.RestrictedSwitchPreference;
 
 import org.junit.After;
 import org.junit.Before;
@@ -68,6 +76,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -81,6 +90,9 @@
 @Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class,
         ShadowUserManager.class, ShadowLockPatternUtils.class})
 public class FingerprintSettingsFragmentTest {
+    private static final int PRIMARY_USER_ID = 0;
+    private static final int GUEST_USER_ID = 10;
+
     private FingerprintSettingsFragment mFragment;
     private Context mContext;
     private FragmentActivity mActivity;
@@ -92,11 +104,26 @@
     @Mock
     private FragmentTransaction mFragmentTransaction;
 
+    @Captor
+    private ArgumentCaptor<CancellationSignal> mCancellationSignalArgumentCaptor =
+            ArgumentCaptor.forClass(CancellationSignal.class);
+    @Captor
+    private ArgumentCaptor<FingerprintManager.AuthenticationCallback>
+            mAuthenticationCallbackArgumentCaptor = ArgumentCaptor.forClass(
+            FingerprintManager.AuthenticationCallback.class);
+
+    private FingerprintAuthenticateSidecar mFingerprintAuthenticateSidecar;
+
     @Before
     public void setUp() {
-        doReturn(true).when(mFingerprintManager).isHardwareDetected();
         ShadowUtils.setFingerprintManager(mFingerprintManager);
         FakeFeatureFactory.setupForTest();
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mFragment = spy(new FingerprintSettingsFragment());
+        doReturn(mContext).when(mFragment).getContext();
+
+        doReturn(true).when(mFingerprintManager).isHardwareDetected();
     }
 
     @After
@@ -146,19 +173,71 @@
                 false)).isTrue();
     }
 
+    // Test the case when FingerprintAuthenticateSidecar receives an error callback from the
+    // framework or from another authentication client. The cancellation signal should not be set
+    // to null because there may exist a running authentication client.
+    // The signal can only be cancelled from the caller in FingerprintSettings.
+    @Test
+    public void testCancellationSignalLifeCycle() {
+        setUpFragment(false);
+
+        mFingerprintAuthenticateSidecar.setFingerprintManager(mFingerprintManager);
+
+        doNothing().when(mFingerprintManager).authenticate(any(),
+                mCancellationSignalArgumentCaptor.capture(),
+                mAuthenticationCallbackArgumentCaptor.capture(), any(), anyInt());
+
+        mFingerprintAuthenticateSidecar.startAuthentication(1);
+
+        assertThat(mAuthenticationCallbackArgumentCaptor.getValue()).isNotNull();
+        assertThat(mCancellationSignalArgumentCaptor.getValue()).isNotNull();
+
+        // Authentication error callback should not cancel the signal.
+        mAuthenticationCallbackArgumentCaptor.getValue().onAuthenticationError(0, "");
+        assertThat(mFingerprintAuthenticateSidecar.isCancelled()).isFalse();
+
+        // The signal should be cancelled when caller stops the authentication.
+        mFingerprintAuthenticateSidecar.stopAuthentication();
+        assertThat(mFingerprintAuthenticateSidecar.isCancelled()).isTrue();
+    }
+
+    @Test
+    public void testGuestUserRequireScreenOnToAuth() {
+        Settings.Secure.putIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+                0,
+                UserHandle.of(PRIMARY_USER_ID).getIdentifier());
+
+        Settings.Secure.putIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
+                1,
+                UserHandle.of(GUEST_USER_ID).getIdentifier());
+
+        setUpFragment(false, GUEST_USER_ID, TYPE_POWER_BUTTON);
+
+        final RestrictedSwitchPreference requireScreenOnToAuthPreference = mFragment.findPreference(
+                KEY_REQUIRE_SCREEN_ON_TO_AUTH);
+        assertThat(requireScreenOnToAuthPreference.isChecked()).isTrue();
+    }
+
     private void setUpFragment(boolean showChooseLock) {
+        setUpFragment(showChooseLock, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL);
+    }
+
+    private void setUpFragment(boolean showChooseLock, int userId,
+            @FingerprintSensorProperties.SensorType int sensorType) {
+        ShadowUserManager.getShadow().addProfile(new UserInfo(userId, "", 0));
+
         Intent intent = new Intent();
         if (!showChooseLock) {
             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]);
             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L);
         }
-
+        intent.putExtra(Intent.EXTRA_USER_ID, userId);
         mActivity = spy(Robolectric.buildActivity(FragmentActivity.class, intent).get());
-        mContext = spy(ApplicationProvider.getApplicationContext());
-
-        mFragment = spy(new FingerprintSettingsFragment());
         doReturn(mActivity).when(mFragment).getActivity();
-        doReturn(mContext).when(mFragment).getContext();
 
         FragmentManager fragmentManager = mock(FragmentManager.class);
         doReturn(mFragmentTransaction).when(fragmentManager).beginTransaction();
@@ -166,9 +245,13 @@
         doReturn(fragmentManager).when(mFragment).getFragmentManager();
         doReturn(fragmentManager).when(mActivity).getSupportFragmentManager();
 
+        mFingerprintAuthenticateSidecar = new FingerprintAuthenticateSidecar();
+        doReturn(mFingerprintAuthenticateSidecar).when(fragmentManager).findFragmentByTag(
+                "authenticate_sidecar");
+
         doNothing().when(mFragment).startActivityForResult(any(Intent.class), anyInt());
 
-        setSensor();
+        setSensor(sensorType);
 
         // Start fragment
         mFragment.onAttach(mContext);
@@ -177,14 +260,14 @@
         mFragment.onResume();
     }
 
-    private void setSensor() {
+    private void setSensor(@FingerprintSensorProperties.SensorType int sensorType) {
         final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
         props.add(new FingerprintSensorPropertiesInternal(
                 0 /* sensorId */,
                 SensorProperties.STRENGTH_STRONG,
                 1 /* maxEnrollmentsPerUser */,
                 new ArrayList<ComponentInfoInternal>(),
-                TYPE_UDFPS_OPTICAL,
+                sensorType,
                 true /* resetLockoutRequiresHardwareAuthToken */));
         doReturn(props).when(mFingerprintManager).getSensorPropertiesInternal();
     }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java
new file mode 100644
index 0000000..0fc0647
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioDeviceTypeControllerTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.bluetooth;
+
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.media.AudioManager;
+
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settingslib.bluetooth.LeAudioProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothDetailsAudioDeviceTypeControllerTest extends
+        BluetoothDetailsControllerTestBase {
+
+    private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
+    private static final String KEY_BT_AUDIO_DEVICE_TYPE = "bluetooth_audio_device_type";
+
+    @Mock
+    private AudioManager mAudioManager;
+    @Mock
+    private Lifecycle mAudioDeviceTypeLifecycle;
+    @Mock
+    private PreferenceCategory mProfilesContainer;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private LocalBluetoothManager mManager;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private LeAudioProfile mLeAudioProfile;
+    private BluetoothDetailsAudioDeviceTypeController mController;
+    private ListPreference mAudioDeviceTypePref;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+        when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
+        when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(MAC_ADDRESS);
+        when(mBluetoothDevice.getType()).thenReturn(DEVICE_TYPE_LE);
+        when(mManager.getProfileManager()).thenReturn(mProfileManager);
+        when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+        when(mLeAudioProfile.isEnabled(mCachedDevice.getDevice())).thenReturn(true);
+
+        mController = new BluetoothDetailsAudioDeviceTypeController(mContext, mFragment, mManager,
+                mCachedDevice, mAudioDeviceTypeLifecycle);
+        mController.mProfilesContainer = mProfilesContainer;
+
+        mController.createAudioDeviceTypePreference(mContext);
+        mAudioDeviceTypePref = mController.getAudioDeviceTypePreference();
+
+        when(mProfilesContainer.findPreference(KEY_BT_AUDIO_DEVICE_TYPE)).thenReturn(
+                mAudioDeviceTypePref);
+    }
+
+    @Test
+    public void createAudioDeviceTypePreference_btDeviceIsCategorized_checkSelection() {
+        int deviceType = AUDIO_DEVICE_CATEGORY_SPEAKER;
+        when(mAudioManager.getBluetoothAudioDeviceCategory(MAC_ADDRESS, /*isBle=*/true)).thenReturn(
+                deviceType);
+
+        mController.createAudioDeviceTypePreference(mContext);
+        mAudioDeviceTypePref = mController.getAudioDeviceTypePreference();
+
+        assertThat(mAudioDeviceTypePref.getValue()).isEqualTo(Integer.toString(deviceType));
+    }
+
+    @Test
+    public void selectDeviceTypeSpeaker_invokeSetBluetoothAudioDeviceType() {
+        int deviceType = AUDIO_DEVICE_CATEGORY_SPEAKER;
+        mAudioDeviceTypePref.setValue(Integer.toString(deviceType));
+
+        mController.onPreferenceChange(mAudioDeviceTypePref, Integer.toString(deviceType));
+
+        verify(mAudioManager).setBluetoothAudioDeviceCategory(eq(MAC_ADDRESS), eq(true),
+                eq(AUDIO_DEVICE_CATEGORY_SPEAKER));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
index 184f521..7c598e0 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
@@ -202,7 +202,7 @@
                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
                         true, BluetoothDevicePreference.SortType.TYPE_FIFO);
         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
-        mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
+        mFragment.getDevicePreferenceMap().put(mCachedBluetoothDevice, preference);
 
         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
@@ -210,7 +210,7 @@
         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
                 BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
 
-        assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
+        assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
     }
 
     @Test
@@ -221,7 +221,7 @@
                         true, BluetoothDevicePreference.SortType.TYPE_FIFO);
         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
         final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
-        mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
+        mFragment.getDevicePreferenceMap().put(mCachedBluetoothDevice, preference);
 
         when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
index 5fbfee8..ce67051 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
@@ -27,7 +27,12 @@
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.os.Bundle;
+import android.view.View;
 
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -53,6 +58,20 @@
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
 
+    private final Lifecycle mFakeLifecycle = new Lifecycle() {
+        @Override
+        public void addObserver(@NonNull LifecycleObserver observer) {}
+
+        @Override
+        public void removeObserver(@NonNull LifecycleObserver observer) {}
+
+        @NonNull
+        @Override
+        public State getCurrentState() {
+            return State.CREATED;
+        }
+    };
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private LocalBluetoothManager mLocalManager;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -74,6 +93,8 @@
                 .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
         doReturn(mFooterPreference).when(mFragment)
                 .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
+        doReturn(new View(mContext)).when(mFragment).getView();
+        doReturn((LifecycleOwner) () -> mFakeLifecycle).when(mFragment).getViewLifecycleOwner();
         doReturn(Collections.emptyList()).when(mDeviceManager).getCachedDevicesCopy();
 
         mFragment.mBluetoothAdapter = mBluetoothAdapter;
@@ -82,7 +103,7 @@
         mFragment.mDeviceListGroup = mAvailableDevicesCategory;
         mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
     }
-//
+
     @Test
     public void initPreferencesFromPreferenceScreen_findPreferences() {
         mFragment.initPreferencesFromPreferenceScreen();
diff --git a/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.java
deleted file mode 100644
index 4f46ce9..0000000
--- a/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2017 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.bluetooth;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothUuid;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.content.Context;
-import android.content.res.Resources;
-
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.core.AbstractPreferenceController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class})
-public class DeviceListPreferenceFragmentTest {
-
-    private static final String FOOTAGE_MAC_STRING = "Bluetooth mac: xxxx";
-
-    @Mock
-    private Resources mResource;
-    @Mock
-    private Context mContext;
-    @Mock
-    private BluetoothLeScanner mBluetoothLeScanner;
-
-    private TestFragment mFragment;
-    private Preference mMyDevicePreference;
-
-
-    private BluetoothAdapter mBluetoothAdapter;
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mFragment = spy(new TestFragment());
-        doReturn(mContext).when(mFragment).getContext();
-        doReturn(mResource).when(mFragment).getResources();
-        mBluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter());
-        mFragment.mBluetoothAdapter = mBluetoothAdapter;
-
-        mMyDevicePreference = new Preference(RuntimeEnvironment.application);
-    }
-
-    @Test
-    public void setUpdateMyDevicePreference_setTitleCorrectly() {
-        doReturn(FOOTAGE_MAC_STRING).when(mFragment)
-            .getString(eq(R.string.bluetooth_footer_mac_message), any());
-
-        mFragment.updateFooterPreference(mMyDevicePreference);
-
-        assertThat(mMyDevicePreference.getTitle()).isEqualTo(FOOTAGE_MAC_STRING);
-    }
-
-    @Test
-    public void testEnableDisableScanning_testStateAfterEanbleDisable() {
-        mFragment.enableScanning();
-        verify(mFragment).startScanning();
-        assertThat(mFragment.mScanEnabled).isTrue();
-
-        mFragment.disableScanning();
-        verify(mFragment).stopScanning();
-        assertThat(mFragment.mScanEnabled).isFalse();
-    }
-
-    @Test
-    public void testScanningStateChanged_testScanStarted() {
-        mFragment.enableScanning();
-        assertThat(mFragment.mScanEnabled).isTrue();
-        verify(mFragment).startScanning();
-
-        mFragment.onScanningStateChanged(true);
-        verify(mFragment, times(1)).startScanning();
-    }
-
-    @Test
-    public void testScanningStateChanged_testScanFinished() {
-        // Could happen when last scanning not done while current scan gets enabled
-        mFragment.enableScanning();
-        verify(mFragment).startScanning();
-        assertThat(mFragment.mScanEnabled).isTrue();
-
-        mFragment.onScanningStateChanged(false);
-        verify(mFragment, times(2)).startScanning();
-    }
-
-    @Test
-    public void testScanningStateChanged_testScanStateMultiple() {
-        // Could happen when last scanning not done while current scan gets enabled
-        mFragment.enableScanning();
-        assertThat(mFragment.mScanEnabled).isTrue();
-        verify(mFragment).startScanning();
-
-        mFragment.onScanningStateChanged(true);
-        verify(mFragment, times(1)).startScanning();
-
-        mFragment.onScanningStateChanged(false);
-        verify(mFragment, times(2)).startScanning();
-
-        mFragment.onScanningStateChanged(true);
-        verify(mFragment, times(2)).startScanning();
-
-        mFragment.disableScanning();
-        verify(mFragment).stopScanning();
-
-        mFragment.onScanningStateChanged(false);
-        verify(mFragment, times(2)).startScanning();
-
-        mFragment.onScanningStateChanged(true);
-        verify(mFragment, times(2)).startScanning();
-    }
-
-    @Test
-    public void testScanningStateChanged_testScanFinishedAfterDisable() {
-        mFragment.enableScanning();
-        verify(mFragment).startScanning();
-        assertThat(mFragment.mScanEnabled).isTrue();
-
-        mFragment.disableScanning();
-        verify(mFragment).stopScanning();
-        assertThat(mFragment.mScanEnabled).isFalse();
-
-        mFragment.onScanningStateChanged(false);
-        verify(mFragment, times(1)).startScanning();
-    }
-
-    @Test
-    public void testScanningStateChanged_testScanStartedAfterDisable() {
-        mFragment.enableScanning();
-        verify(mFragment).startScanning();
-        assertThat(mFragment.mScanEnabled).isTrue();
-
-        mFragment.disableScanning();
-        verify(mFragment).stopScanning();
-        assertThat(mFragment.mScanEnabled).isFalse();
-
-        mFragment.onScanningStateChanged(true);
-        verify(mFragment, times(1)).startScanning();
-    }
-
-    @Test
-    public void startScanning_setLeScanFilter_shouldStartLeScan() {
-        final ScanFilter leScanFilter = new ScanFilter.Builder()
-                .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}, new byte[]{0})
-                .build();
-        doReturn(mBluetoothLeScanner).when(mBluetoothAdapter).getBluetoothLeScanner();
-
-        mFragment.setFilter(Collections.singletonList(leScanFilter));
-        mFragment.startScanning();
-
-        verify(mBluetoothLeScanner).startScan(eq(Collections.singletonList(leScanFilter)),
-                any(ScanSettings.class), any(ScanCallback.class));
-    }
-
-    /**
-     * Fragment to test since {@code DeviceListPreferenceFragment} is abstract
-     */
-    public static class TestFragment extends DeviceListPreferenceFragment {
-
-        public TestFragment() {
-            super("");
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return 0;
-        }
-
-        @Override
-        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {}
-
-        @Override
-        protected void initPreferencesFromPreferenceScreen() {}
-
-        @Override
-        public String getDeviceListKey() {
-            return null;
-        }
-
-        @Override
-        protected String getLogTag() {
-            return null;
-        }
-
-        @Override
-        protected int getPreferenceScreenResId() {
-            return 0;
-        }
-
-        @Override
-        protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-            return null;
-        }
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.kt b/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.kt
new file mode 100644
index 0000000..5a21aff
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/DeviceListPreferenceFragmentTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothUuid
+import android.bluetooth.le.BluetoothLeScanner
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanFilter
+import android.content.Context
+import android.content.res.Resources
+import androidx.preference.Preference
+import com.android.settings.R
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(RobolectricTestRunner::class)
+@Config(shadows = [ShadowBluetoothAdapter::class])
+class DeviceListPreferenceFragmentTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var resource: Resources
+
+    @Mock
+    private lateinit var context: Context
+
+    @Mock
+    private lateinit var bluetoothLeScanner: BluetoothLeScanner
+
+    @Mock
+    private lateinit var cachedDeviceManager: CachedBluetoothDeviceManager
+
+    @Mock
+    private lateinit var cachedDevice: CachedBluetoothDevice
+
+    @Spy
+    private var fragment = TestFragment()
+
+    private lateinit var myDevicePreference: Preference
+    private lateinit var bluetoothAdapter: BluetoothAdapter
+
+    @Before
+    fun setUp() {
+        doReturn(context).`when`(fragment).context
+        doReturn(resource).`when`(fragment).resources
+        doNothing().`when`(fragment).onDeviceAdded(cachedDevice)
+        bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter())
+        fragment.mBluetoothAdapter = bluetoothAdapter
+        fragment.mCachedDeviceManager = cachedDeviceManager
+
+        myDevicePreference = Preference(RuntimeEnvironment.application)
+    }
+
+    @Test
+    fun setUpdateMyDevicePreference_setTitleCorrectly() {
+        doReturn(FOOTAGE_MAC_STRING).`when`(fragment)
+            .getString(eq(R.string.bluetooth_footer_mac_message), any())
+
+        fragment.updateFooterPreference(myDevicePreference)
+
+        assertThat(myDevicePreference.title).isEqualTo(FOOTAGE_MAC_STRING)
+    }
+
+    @Test
+    fun testEnableDisableScanning_testStateAfterEnableDisable() {
+        fragment.enableScanning()
+        verify(fragment).startScanning()
+        assertThat(fragment.mScanEnabled).isTrue()
+
+        fragment.disableScanning()
+        verify(fragment).stopScanning()
+        assertThat(fragment.mScanEnabled).isFalse()
+    }
+
+    @Test
+    fun testScanningStateChanged_testScanStarted() {
+        fragment.enableScanning()
+        assertThat(fragment.mScanEnabled).isTrue()
+        verify(fragment).startScanning()
+
+        fragment.onScanningStateChanged(true)
+        verify(fragment, times(1)).startScanning()
+    }
+
+    @Test
+    fun testScanningStateChanged_testScanFinished() {
+        // Could happen when last scanning not done while current scan gets enabled
+        fragment.enableScanning()
+        verify(fragment).startScanning()
+        assertThat(fragment.mScanEnabled).isTrue()
+
+        fragment.onScanningStateChanged(false)
+        verify(fragment, times(2)).startScanning()
+    }
+
+    @Test
+    fun testScanningStateChanged_testScanStateMultiple() {
+        // Could happen when last scanning not done while current scan gets enabled
+        fragment.enableScanning()
+        assertThat(fragment.mScanEnabled).isTrue()
+        verify(fragment).startScanning()
+
+        fragment.onScanningStateChanged(true)
+        verify(fragment, times(1)).startScanning()
+
+        fragment.onScanningStateChanged(false)
+        verify(fragment, times(2)).startScanning()
+
+        fragment.onScanningStateChanged(true)
+        verify(fragment, times(2)).startScanning()
+
+        fragment.disableScanning()
+        verify(fragment).stopScanning()
+
+        fragment.onScanningStateChanged(false)
+        verify(fragment, times(2)).startScanning()
+
+        fragment.onScanningStateChanged(true)
+        verify(fragment, times(2)).startScanning()
+    }
+
+    @Test
+    fun testScanningStateChanged_testScanFinishedAfterDisable() {
+        fragment.enableScanning()
+        verify(fragment).startScanning()
+        assertThat(fragment.mScanEnabled).isTrue()
+
+        fragment.disableScanning()
+        verify(fragment).stopScanning()
+        assertThat(fragment.mScanEnabled).isFalse()
+
+        fragment.onScanningStateChanged(false)
+        verify(fragment, times(1)).startScanning()
+    }
+
+    @Test
+    fun testScanningStateChanged_testScanStartedAfterDisable() {
+        fragment.enableScanning()
+        verify(fragment).startScanning()
+        assertThat(fragment.mScanEnabled).isTrue()
+
+        fragment.disableScanning()
+        verify(fragment).stopScanning()
+        assertThat(fragment.mScanEnabled).isFalse()
+
+        fragment.onScanningStateChanged(true)
+        verify(fragment, times(1)).startScanning()
+    }
+
+    @Test
+    fun startScanning_setLeScanFilter_shouldStartLeScan() {
+        val leScanFilter = ScanFilter.Builder()
+            .setServiceData(BluetoothUuid.HEARING_AID, byteArrayOf(0), byteArrayOf(0))
+            .build()
+        doReturn(bluetoothLeScanner).`when`(bluetoothAdapter).bluetoothLeScanner
+
+        fragment.setFilter(listOf(leScanFilter))
+        fragment.startScanning()
+
+        verify(bluetoothLeScanner).startScan(eq(listOf(leScanFilter)), any(), any<ScanCallback>())
+    }
+
+    @Test
+    fun addCachedDevices_whenFilterIsNull_onDeviceAddedIsCalled() = runBlocking {
+        val mockCachedDevice = mock(CachedBluetoothDevice::class.java)
+        whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(mockCachedDevice))
+        fragment.lifecycleScope = this
+
+        fragment.addCachedDevices(filterForCachedDevices = null)
+        delay(100)
+
+        verify(fragment).onDeviceAdded(mockCachedDevice)
+    }
+
+    @Test
+    fun addCachedDevices_whenFilterMatched_onDeviceAddedIsCalled() = runBlocking {
+        val mockBluetoothDevice = mock(BluetoothDevice::class.java)
+        whenever(mockBluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+        whenever(cachedDevice.device).thenReturn(mockBluetoothDevice)
+        whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(cachedDevice))
+        fragment.lifecycleScope = this
+
+        fragment.addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER)
+        delay(100)
+
+        verify(fragment).onDeviceAdded(cachedDevice)
+    }
+
+    @Test
+    fun addCachedDevices_whenFilterNoMatch_onDeviceAddedNotCalled() = runBlocking {
+        val mockBluetoothDevice = mock(BluetoothDevice::class.java)
+        whenever(mockBluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+        whenever(cachedDevice.device).thenReturn(mockBluetoothDevice)
+        whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(cachedDevice))
+        fragment.lifecycleScope = this
+
+        fragment.addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER)
+        delay(100)
+
+        verify(fragment, never()).onDeviceAdded(cachedDevice)
+    }
+
+    /**
+     * Fragment to test since `DeviceListPreferenceFragment` is abstract
+     */
+    open class TestFragment : DeviceListPreferenceFragment(null) {
+        override fun getMetricsCategory() = 0
+        override fun initPreferencesFromPreferenceScreen() {}
+        override val deviceListKey = "device_list"
+        override fun getLogTag() = null
+        override fun getPreferenceScreenResId() = 0
+    }
+
+    private companion object {
+        const val FOOTAGE_MAC_STRING = "Bluetooth mac: xxxx"
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
index f4fa397..9538092 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
@@ -18,6 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
@@ -27,13 +31,17 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Dialog;
 import android.app.role.RoleManager;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.view.InputDevice;
@@ -48,6 +56,8 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.R;
+import com.android.settings.dashboard.profileselector.UserAdapter;
+import com.android.settingslib.PrimarySwitchPreference;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
@@ -59,7 +69,9 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 public class StylusDevicesControllerTest {
@@ -79,6 +91,8 @@
     @Mock
     private PackageManager mPm;
     @Mock
+    private UserManager mUserManager;
+    @Mock
     private RoleManager mRm;
     @Mock
     private Lifecycle mLifecycle;
@@ -87,7 +101,6 @@
     @Mock
     private BluetoothDevice mBluetoothDevice;
 
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -101,6 +114,7 @@
 
         when(mContext.getSystemService(InputMethodManager.class)).thenReturn(mImm);
         when(mContext.getSystemService(RoleManager.class)).thenReturn(mRm);
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
         doNothing().when(mContext).startActivity(any());
 
         when(mImm.getCurrentInputMethodInfo()).thenReturn(mInputMethodInfo);
@@ -115,6 +129,8 @@
         when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME),
                 any(PackageManager.ApplicationInfoFlags.class))).thenReturn(new ApplicationInfo());
         when(mPm.getApplicationLabel(any(ApplicationInfo.class))).thenReturn(NOTES_APP_LABEL);
+        when(mUserManager.getUsers()).thenReturn(Arrays.asList(new UserInfo(0, "default", 0)));
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
 
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
 
@@ -228,22 +244,50 @@
         when(mInputMethodInfo.supportsStylusHandwriting()).thenReturn(false);
 
         showScreen(mController);
-        Preference handwritingPref = mPreferenceContainer.getPreference(1);
 
+        Preference handwritingPref = mPreferenceContainer.getPreference(1);
         assertThat(handwritingPref.isVisible()).isFalse();
     }
 
     @Test
-    public void defaultNotesPreference_showsNotesRoleApp() {
+    public void defaultNotesPreference_singleUser_showsNotesRoleApp() {
         showScreen(mController);
-        Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
 
+        Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
         assertThat(defaultNotesPref.getTitle().toString()).isEqualTo(
                 mContext.getString(R.string.stylus_default_notes_app));
         assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(NOTES_APP_LABEL.toString());
     }
 
     @Test
+    public void defaultNotesPreference_workProfileUser_showsWorkNotesRoleApp() {
+        when(mUserManager.isManagedProfile(0)).thenReturn(true);
+
+        showScreen(mController);
+
+        Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
+        assertThat(defaultNotesPref.getTitle().toString()).isEqualTo(
+                mContext.getString(R.string.stylus_default_notes_app));
+        assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(
+                mContext.getString(R.string.stylus_default_notes_summary_work,
+                        NOTES_APP_LABEL.toString()));
+    }
+
+    @Test
+    public void defaultNotesPreference_noApplicationInfo_showsBlankSummary()
+            throws PackageManager.NameNotFoundException {
+        when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME),
+                any(PackageManager.ApplicationInfoFlags.class))).thenReturn(null);
+
+        showScreen(mController);
+
+        Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
+        assertThat(defaultNotesPref.getTitle().toString()).isEqualTo(
+                mContext.getString(R.string.stylus_default_notes_app));
+        assertThat(defaultNotesPref.getSummary().toString()).isEqualTo("");
+    }
+
+    @Test
     public void defaultNotesPreference_roleHolderChanges_updatesPreference() {
         showScreen(mController);
         Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
@@ -267,7 +311,7 @@
     }
 
     @Test
-    public void defaultNotesPreferenceClick_sendsManageDefaultRoleIntent() {
+    public void defaultNotesPreferenceClick_singleUser_sendsManageDefaultRoleIntent() {
         final String permissionPackageName = "permissions.package";
         when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName);
         final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -282,6 +326,76 @@
         assertThat(intent.getPackage()).isEqualTo(permissionPackageName);
         assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(
                 RoleManager.ROLE_NOTES);
+        assertNull(mController.mDialog);
+    }
+
+    @Test
+    public void defaultNotesPreferenceClick_multiUserManagedProfile_showsProfileSelectorDialog() {
+        mContext.setTheme(R.style.Theme_AppCompat);
+        final String permissionPackageName = "permissions.package";
+        final UserHandle currentUser = Process.myUserHandle();
+        List<UserInfo> userInfos = Arrays.asList(
+                new UserInfo(currentUser.getIdentifier(), "current", 0),
+                new UserInfo(1, "profile", UserInfo.FLAG_PROFILE)
+        );
+        when(mUserManager.getUsers()).thenReturn(userInfos);
+        when(mUserManager.isManagedProfile(1)).thenReturn(true);
+        when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(userInfos.get(0));
+        when(mUserManager.getUserInfo(1)).thenReturn(userInfos.get(1));
+        when(mUserManager.getProfileParent(1)).thenReturn(userInfos.get(0));
+        when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName);
+
+        showScreen(mController);
+        Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
+        mController.onPreferenceClick(defaultNotesPref);
+
+        assertTrue(mController.mDialog.isShowing());
+    }
+
+    @Test
+    public void defaultNotesPreferenceClick_noManagedProfile_sendsManageDefaultRoleIntent() {
+        final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        mContext.setTheme(R.style.Theme_AppCompat);
+        final String permissionPackageName = "permissions.package";
+        final UserHandle currentUser = Process.myUserHandle();
+        List<UserInfo> userInfos = Arrays.asList(
+                new UserInfo(currentUser.getIdentifier(), "current", 0),
+                new UserInfo(1, "other", UserInfo.FLAG_FULL)
+        );
+        when(mUserManager.getUsers()).thenReturn(userInfos);
+        when(mUserManager.isManagedProfile(1)).thenReturn(false);
+        when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(userInfos.get(0));
+        when(mUserManager.getUserInfo(1)).thenReturn(userInfos.get(1));
+        when(mUserManager.getProfileParent(any())).thenReturn(null);
+        when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName);
+
+        showScreen(mController);
+        Preference defaultNotesPref = mPreferenceContainer.getPreference(0);
+        mController.onPreferenceClick(defaultNotesPref);
+
+        verify(mContext).startActivity(captor.capture());
+        Intent intent = captor.getValue();
+        assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MANAGE_DEFAULT_APP);
+        assertThat(intent.getPackage()).isEqualTo(permissionPackageName);
+        assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(
+                RoleManager.ROLE_NOTES);
+        assertNull(mController.mDialog);
+    }
+
+    @Test
+    public void profileSelectDialogClickCallback_onClick_sendsIntent() {
+        Intent intent = new Intent();
+        UserHandle user1 = mock(UserHandle.class);
+        UserHandle user2 = mock(UserHandle.class);
+        List<UserHandle> users = Arrays.asList(user1, user2);
+        mController.mDialog = new Dialog(mContext);
+        UserAdapter.OnClickListener callback = mController
+                .createProfileDialogClickCallback(intent, users);
+
+        callback.onClick(1);
+
+        assertEquals(intent.getExtra(Intent.EXTRA_USER), user2);
+        verify(mContext).startActivity(intent);
     }
 
     @Test
@@ -290,9 +404,10 @@
                 Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1);
 
         showScreen(mController);
-        SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
 
-        assertThat(handwritingPref.isChecked()).isEqualTo(true);
+        assertThat(handwritingPref.getCheckedState()).isEqualTo(true);
     }
 
     @Test
@@ -301,9 +416,10 @@
                 Settings.Secure.STYLUS_HANDWRITING_ENABLED, 0);
 
         showScreen(mController);
-        SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
 
-        assertThat(handwritingPref.isChecked()).isEqualTo(false);
+        assertThat(handwritingPref.getCheckedState()).isEqualTo(false);
     }
 
     @Test
@@ -311,21 +427,20 @@
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.STYLUS_HANDWRITING_ENABLED, 0);
         showScreen(mController);
-        SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
 
-        handwritingPref.performClick();
+        handwritingPref.callChangeListener(true);
 
-        assertThat(handwritingPref.isChecked()).isEqualTo(true);
         assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.STYLUS_HANDWRITING_ENABLED, -1)).isEqualTo(1);
     }
 
     @Test
-    public void handwritingPreference_startsHandwritingSettingsOnClickIfChecked() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.STYLUS_HANDWRITING_ENABLED, 0);
+    public void handwritingPreference_startsHandwritingSettingsOnClick() {
         showScreen(mController);
-        SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
 
         handwritingPref.performClick();
 
@@ -334,11 +449,23 @@
     }
 
     @Test
-    public void handwritingPreference_doesNotStartHandwritingSettingsOnClickIfNotChecked() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1);
+    public void handwritingPreference_doesNotStartHandwritingSettingsOnChange() {
         showScreen(mController);
-        SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
+
+        handwritingPref.callChangeListener(true);
+
+        verify(mInputMethodInfo, times(0)).createStylusHandwritingSettingsActivityIntent();
+        verify(mContext, times(0)).startActivity(any());
+    }
+
+    @Test
+    public void handwritingPreference_doesNotCreateIntentIfNoInputMethod() {
+        when(mImm.getCurrentInputMethodInfo()).thenReturn(null);
+        showScreen(mController);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
 
         handwritingPref.performClick();
 
@@ -350,14 +477,12 @@
     public void handwritingPreference_doesNotStartHandwritingSettingsIfNoIntent() {
         when(mInputMethodInfo.createStylusHandwritingSettingsActivityIntent())
                 .thenReturn(null);
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1);
         showScreen(mController);
-        SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1);
+        PrimarySwitchPreference handwritingPref =
+                (PrimarySwitchPreference) mPreferenceContainer.getPreference(1);
 
         handwritingPref.performClick();
 
-        verify(mInputMethodInfo, times(0)).createStylusHandwritingSettingsActivityIntent();
         verify(mContext, times(0)).startActivity(any());
     }
 
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsbFirmwareControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsbFirmwareControllerTest.java
new file mode 100644
index 0000000..2ba655a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsbFirmwareControllerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Collections;
+import java.util.HashMap;
+
+@RunWith(RobolectricTestRunner.class)
+public class StylusUsbFirmwareControllerTest {
+
+    private Context mContext;
+    private FakeFeatureFactory mFeatureFactory;
+    private Lifecycle mLifecycle;
+    private PreferenceScreen mScreen;
+
+    private StylusUsbFirmwareController mController;
+    @Mock
+    private StylusUsiDetailsFragment mFragment;
+    @Mock
+    private UsbManager mUsbManager;
+    private PreferenceCategory mPreferenceCategory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        mLifecycle = new Lifecycle(() -> mLifecycle);
+
+        when(mFragment.getContext()).thenReturn(mContext);
+
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+        mController = new StylusUsbFirmwareController(mContext, "stylus_usb_firmware");
+
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        mScreen = preferenceManager.createPreferenceScreen(mContext);
+
+        mPreferenceCategory = new PreferenceCategory(mContext);
+        mPreferenceCategory.setKey(mController.getPreferenceKey());
+    }
+
+    @Test
+    public void displayPreference_featurePresentUsbStylusAttached_preferenceAdded() {
+        attachUsbDevice();
+        enableFullStylusFeature();
+
+        mController.displayPreference(mScreen);
+
+        assertNotNull(mScreen.findPreference("stylus_usb_firmware"));
+    }
+
+    @Test
+    public void displayPreference_featureAbsentUsbStylusAttached_preferenceNotAdded() {
+        attachUsbDevice();
+        mController.mUsbConnectionListener.onUsbStylusConnectionChanged(
+                mock(UsbDevice.class), true);
+
+        mController.displayPreference(mScreen);
+
+        assertNull(mScreen.findPreference(mController.getPreferenceKey()));
+    }
+
+    @Test
+    public void onUsbStylusConnectionChanged_featurePresentUsbStylusAttached_preferenceAdded() {
+        mController.displayPreference(mScreen);
+
+        attachUsbDevice();
+        enableFullStylusFeature();
+        mController.mUsbConnectionListener.onUsbStylusConnectionChanged(
+                mock(UsbDevice.class), true);
+
+        assertNotNull(mScreen.findPreference(mController.getPreferenceKey()));
+    }
+
+    @Test
+    public void onUsbStylusConnectionChanged_featureAbsentUsbStylusAttached_preferenceRemoved() {
+        mController.displayPreference(mScreen);
+
+        attachUsbDevice();
+        mController.mUsbConnectionListener.onUsbStylusConnectionChanged(
+                mock(UsbDevice.class), true);
+
+        assertNull(mScreen.findPreference(mController.getPreferenceKey()));
+    }
+
+    @Test
+    public void hasUsbStylusFirmwareUpdateFeature_featurePresent_true() {
+        when(mFeatureFactory.getStylusFeatureProvider()
+                .isUsbFirmwareUpdateEnabled(any())).thenReturn(true);
+        attachUsbDevice();
+
+        assertTrue(StylusUsbFirmwareController
+                .hasUsbStylusFirmwareUpdateFeature(mock(UsbDevice.class)));
+    }
+
+    @Test
+    public void hasUsbStylusFirmwareUpdateFeature_featureNotPresent_false() {
+        when(mFeatureFactory.getStylusFeatureProvider()
+                .isUsbFirmwareUpdateEnabled(any())).thenReturn(false);
+        attachUsbDevice();
+
+        assertFalse(StylusUsbFirmwareController
+                .hasUsbStylusFirmwareUpdateFeature(mock(UsbDevice.class)));
+    }
+
+    private void attachUsbDevice() {
+        when(mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
+        HashMap<String, UsbDevice> deviceList = new HashMap<>();
+        deviceList.put("0", mock(UsbDevice.class));
+        when(mUsbManager.getDeviceList()).thenReturn(deviceList);
+    }
+
+    private void enableFullStylusFeature() {
+        when(mFeatureFactory.getStylusFeatureProvider()
+                .isUsbFirmwareUpdateEnabled(any())).thenReturn(true);
+        when(mFeatureFactory.getStylusFeatureProvider()
+                .getUsbFirmwareUpdatePreferences(any(), any()))
+                .thenReturn(Collections.singletonList(mock(Preference.class)));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/UsbStylusBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/UsbStylusBroadcastReceiverTest.java
new file mode 100644
index 0000000..03279ee
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/UsbStylusBroadcastReceiverTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.stylus;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class UsbStylusBroadcastReceiverTest {
+    private Context mContext;
+    private UsbStylusBroadcastReceiver mReceiver;
+    private FakeFeatureFactory mFeatureFactory;
+    @Mock
+    private UsbStylusBroadcastReceiver.UsbStylusConnectionListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mReceiver = new UsbStylusBroadcastReceiver(mContext, mListener);
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+    }
+
+    @Test
+    public void onReceive_usbDeviceAttachedStylus_invokeCallback() {
+        when(mFeatureFactory.mStylusFeatureProvider.isUsbFirmwareUpdateEnabled(any()))
+                .thenReturn(true);
+        final UsbDevice usbDevice = mock(UsbDevice.class);
+        final Intent intent = new Intent();
+        intent.setAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mListener).onUsbStylusConnectionChanged(usbDevice, true);
+    }
+
+    @Test
+    public void onReceive_usbDeviceDetachedStylus_invokeCallback() {
+        when(mFeatureFactory.mStylusFeatureProvider.isUsbFirmwareUpdateEnabled(any()))
+                .thenReturn(true);
+        final UsbDevice usbDevice = mock(UsbDevice.class);
+        final Intent intent = new Intent();
+        intent.setAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mListener).onUsbStylusConnectionChanged(usbDevice, false);
+    }
+
+    @Test
+    public void onReceive_usbDeviceAttachedNotStylus_doesNotInvokeCallback() {
+        when(mFeatureFactory.mStylusFeatureProvider.isUsbFirmwareUpdateEnabled(any()))
+                .thenReturn(false);
+        final UsbDevice usbDevice = mock(UsbDevice.class);
+        final Intent intent = new Intent();
+        intent.setAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void onReceive_usbDeviceStateStylus_invokeCallback() {
+        when(mFeatureFactory.mStylusFeatureProvider.isUsbFirmwareUpdateEnabled(any()))
+                .thenReturn(true);
+        final UsbDevice usbDevice = mock(UsbDevice.class);
+        final Intent intent = new Intent();
+        intent.setAction(UsbManager.ACTION_USB_STATE);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verify(mListener).onUsbStylusConnectionChanged(usbDevice, false);
+    }
+
+    @Test
+    public void onReceive_usbDeviceStateNotStylus_doesNotInvokeCallback() {
+        when(mFeatureFactory.mStylusFeatureProvider.isUsbFirmwareUpdateEnabled(any()))
+                .thenReturn(false);
+        final UsbDevice usbDevice = mock(UsbDevice.class);
+        final Intent intent = new Intent();
+        intent.setAction(UsbManager.ACTION_USB_STATE);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, usbDevice);
+
+        mReceiver.onReceive(mContext, intent);
+
+        verifyNoMoreInteractions(mListener);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/development/ShowKeyPressesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/ShowKeyPressesPreferenceControllerTest.java
new file mode 100644
index 0000000..b7fb902
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/ShowKeyPressesPreferenceControllerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class ShowKeyPressesPreferenceControllerTest {
+
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private SwitchPreference mPreference;
+
+    private Context mContext;
+
+    private ShowKeyPressesPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mController = new ShowKeyPressesPreferenceController(mContext);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+        mController.displayPreference(mScreen);
+    }
+
+    @Test
+    public void updateState_showKeyPressesEnabled_shouldCheckedPreference() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, ShowTapsPreferenceController.SETTING_VALUE_ON);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(true);
+    }
+
+    @Test
+    public void updateState_showKeyPressesDisabled_shouldUncheckedPreference() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, ShowTapsPreferenceController.SETTING_VALUE_OFF);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(false);
+    }
+
+    @Test
+    public void onPreferenceChange_preferenceChecked_shouldEnableShowKeyPresses() {
+        mController.onPreferenceChange(mPreference, true /* new value */);
+
+        final int showKeyPresses = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, -1 /* default */);
+
+        assertThat(showKeyPresses).isEqualTo(ShowTapsPreferenceController.SETTING_VALUE_ON);
+    }
+
+    @Test
+    public void onPreferenceChange_preferenceUnchecked_shouldDisableShowKeyPresses() {
+        mController.onPreferenceChange(mPreference, false /* new value */);
+
+        final int showTapsMode = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, -1 /* default */);
+
+        assertThat(showTapsMode).isEqualTo(ShowTapsPreferenceController.SETTING_VALUE_OFF);
+    }
+
+    @Test
+    public void onDeveloperOptionsSwitchDisabled_preferenceShouldBeEnabled() {
+        mController.onDeveloperOptionsSwitchDisabled();
+
+        final int showTapsMode = Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.SHOW_KEY_PRESSES, -1 /* default */);
+
+        assertThat(showTapsMode).isEqualTo(ShowTapsPreferenceController.SETTING_VALUE_OFF);
+        verify(mPreference).setEnabled(false);
+        verify(mPreference).setChecked(false);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceControllerTest.java
new file mode 100644
index 0000000..4d1b4d0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceControllerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatteryCycleCountPreferenceControllerTest {
+    private BatteryCycleCountPreferenceController mController;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mController = new BatteryCycleCountPreferenceController(mContext,
+                "battery_info_cycle_count");
+    }
+
+    @Test
+    public void getAvailabilityStatus_returnAvailable() {
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getSummary_returnExpectedResult() {
+        final Intent batteryIntent = new Intent();
+        batteryIntent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, 10);
+        doReturn(batteryIntent).when(mContext).registerReceiver(any(), any());
+
+        assertThat(mController.getSummary()).isEqualTo("10");
+    }
+
+    @Test
+    public void getSummary_noValue_returnUnavailable() {
+        final Intent batteryIntent = new Intent();
+        doReturn(batteryIntent).when(mContext).registerReceiver(any(), any());
+
+        assertThat(mController.getSummary()).isEqualTo(
+                mContext.getText(R.string.battery_cycle_count_not_available));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryFirstUseDatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryFirstUseDatePreferenceControllerTest.java
new file mode 100644
index 0000000..ff8ea62
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryFirstUseDatePreferenceControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.os.BatteryManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowBatteryManager;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBatteryManager.class})
+public class BatteryFirstUseDatePreferenceControllerTest {
+    private BatteryFirstUseDatePreferenceController mController;
+    private Context mContext;
+    private BatteryManager mBatteryManager;
+    private ShadowBatteryManager mShadowBatteryManager;
+    private FakeFeatureFactory mFactory;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mBatteryManager = mContext.getSystemService(BatteryManager.class);
+        mShadowBatteryManager = shadowOf(mBatteryManager);
+        mFactory = FakeFeatureFactory.setupForTest();
+        mController = new BatteryFirstUseDatePreferenceController(mContext,
+                "battery_info_first_use_date");
+    }
+
+    @Test
+    public void getAvailabilityStatus_dateAvailable_returnAvailable() {
+        when(mFactory.batterySettingsFeatureProvider.isFirstUseDateAvailable(eq(mContext),
+                anyLong())).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_dateUnavailable_returnNotAvailable() {
+        when(mFactory.batterySettingsFeatureProvider.isFirstUseDateAvailable(eq(mContext),
+                anyLong())).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getSummary_available_returnExpectedDate() {
+        when(mFactory.batterySettingsFeatureProvider.isFirstUseDateAvailable(eq(mContext),
+                anyLong())).thenReturn(true);
+        mShadowBatteryManager.setLongProperty(BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE,
+                1669680000L);
+
+        final CharSequence result = mController.getSummary();
+
+        assertThat(result.toString()).isEqualTo("November 29, 2022");
+    }
+
+    @Test
+    public void getSummary_unavailable_returnNull() {
+        when(mFactory.batterySettingsFeatureProvider.isFirstUseDateAvailable(eq(mContext),
+                anyLong())).thenReturn(false);
+
+        assertThat(mController.getSummary()).isNull();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryManufactureDatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryManufactureDatePreferenceControllerTest.java
new file mode 100644
index 0000000..608ce00
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/batteryinfo/BatteryManufactureDatePreferenceControllerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.batteryinfo;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.os.BatteryManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowBatteryManager;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBatteryManager.class})
+public class BatteryManufactureDatePreferenceControllerTest {
+
+    private BatteryManufactureDatePreferenceController mController;
+    private Context mContext;
+    private BatteryManager mBatteryManager;
+    private ShadowBatteryManager mShadowBatteryManager;
+    private FakeFeatureFactory mFactory;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mBatteryManager = mContext.getSystemService(BatteryManager.class);
+        mShadowBatteryManager = shadowOf(mBatteryManager);
+        mFactory = FakeFeatureFactory.setupForTest();
+        mController = new BatteryManufactureDatePreferenceController(mContext,
+                "battery_info_manufacture_date");
+    }
+
+    @Test
+    public void getAvailabilityStatus_dateAvailable_returnAvailable() {
+        when(mFactory.batterySettingsFeatureProvider.isManufactureDateAvailable(eq(mContext),
+                anyLong())).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_dateUnavailable_returnNotAvailable() {
+        when(mFactory.batterySettingsFeatureProvider.isManufactureDateAvailable(eq(mContext),
+                anyLong())).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getSummary_available_returnExpectedDate() {
+        when(mFactory.batterySettingsFeatureProvider.isManufactureDateAvailable(eq(mContext),
+                anyLong())).thenReturn(true);
+        mShadowBatteryManager.setLongProperty(BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE,
+                1669680000L);
+
+        final CharSequence result = mController.getSummary();
+
+        assertThat(result.toString()).isEqualTo("November 29, 2022");
+    }
+
+    @Test
+    public void getSummary_unavailable_returnNull() {
+        when(mFactory.batterySettingsFeatureProvider.isManufactureDateAvailable(eq(mContext),
+                anyLong())).thenReturn(false);
+
+        assertThat(mController.getSummary()).isNull();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/StayAwakeOnFoldPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/StayAwakeOnFoldPreferenceControllerTest.java
new file mode 100644
index 0000000..c994818
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/StayAwakeOnFoldPreferenceControllerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.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 static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class StayAwakeOnFoldPreferenceControllerTest {
+
+    @Mock
+    private Resources mResources;
+    private Context mContext;
+    private StayAwakeOnFoldPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mResources = Mockito.mock(Resources.class);
+        mController = new StayAwakeOnFoldPreferenceController(mContext, "key", mResources);
+    }
+
+    @Test
+    public void getAvailabilityStatus_withConfigNoShow_returnUnsupported() {
+        when(mResources.getBoolean(R.bool.config_stay_awake_on_fold)).thenReturn(false);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_withConfigNoShow_returnAvailable() {
+        when(mResources.getBoolean(R.bool.config_stay_awake_on_fold)).thenReturn(true);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    public void setChecked_enableStayAwakeOnFold_setChecked() {
+        mController.setChecked(true);
+
+        assertThat(isStayAwakeOnFoldEnabled())
+                .isTrue();
+    }
+
+    @Test
+    public void setChecked_disableStayAwakeOnFold_setUnchecked() {
+        mController.setChecked(false);
+
+        assertThat(isStayAwakeOnFoldEnabled())
+                .isFalse();
+    }
+
+    @Test
+    public void isChecked_enableStayAwakeOnFold_returnTrue() {
+        enableStayAwakeOnFoldPreference();
+
+        assertThat(mController.isChecked()).isTrue();
+    }
+
+    @Test
+    public void isChecked_disableStayAwakeOnFold_returnFalse() {
+        disableStayAwakeOnFoldPreference();
+
+        assertThat(mController.isChecked()).isFalse();
+    }
+
+    private void enableStayAwakeOnFoldPreference() {
+        Settings.System.putInt(
+                mContext.getContentResolver(),
+                Settings.System.STAY_AWAKE_ON_FOLD,
+                1);
+    }
+
+    private void disableStayAwakeOnFoldPreference() {
+        Settings.System.putInt(
+                mContext.getContentResolver(),
+                Settings.System.STAY_AWAKE_ON_FOLD,
+                0);
+    }
+
+    private boolean isStayAwakeOnFoldEnabled() {
+        return (Settings.System.getInt(
+                mContext.getContentResolver(),
+                Settings.System.STAY_AWAKE_ON_FOLD,
+                0) == 1);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtilsTest.java
similarity index 60%
rename from tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java
rename to tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtilsTest.java
index cb5de7d..87de62f 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtilsTest.java
@@ -33,7 +33,7 @@
 import java.io.StringWriter;
 
 @RunWith(RobolectricTestRunner.class)
-public final class BatteryHistoricalLogUtilTest {
+public final class BatteryOptimizeLogUtilsTest {
 
     private final StringWriter mTestStringWriter = new StringWriter();
     private final PrintWriter mTestPrintWriter = new PrintWriter(mTestStringWriter);
@@ -43,19 +43,19 @@
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
-        BatteryHistoricalLogUtil.getSharedPreferences(mContext).edit().clear().commit();
+        BatteryOptimizeLogUtils.getSharedPreferences(mContext).edit().clear().commit();
     }
 
     @Test
     public void printHistoricalLog_withDefaultLogs() {
-        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
+        BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
         assertThat(mTestStringWriter.toString()).contains("nothing to dump");
     }
 
     @Test
     public void writeLog_withExpectedLogs() {
-        BatteryHistoricalLogUtil.writeLog(mContext, Action.APPLY, "pkg1", "logs");
-        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
+        BatteryOptimizeLogUtils.writeLog(mContext, Action.APPLY, "pkg1", "logs");
+        BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
 
         assertThat(mTestStringWriter.toString()).contains(
                 "pkg1\taction:APPLY\tevent:logs");
@@ -63,21 +63,27 @@
 
     @Test
     public void writeLog_multipleLogs_withCorrectCounts() {
-        for (int i = 0; i < BatteryHistoricalLogUtil.MAX_ENTRIES; i++) {
-            BatteryHistoricalLogUtil.writeLog(mContext, Action.LEAVE, "pkg" + i, "logs");
+        final int expectedCount = 10;
+        for (int i = 0; i < expectedCount; i++) {
+            BatteryOptimizeLogUtils.writeLog(mContext, Action.LEAVE, "pkg" + i, "logs");
         }
-        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
+        BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
 
-        assertThat(mTestStringWriter.toString().split("LEAVE").length).isEqualTo(41);
+        assertActionCount("LEAVE", expectedCount);
     }
 
     @Test
     public void writeLog_overMaxEntriesLogs_withCorrectCounts() {
-        for (int i = 0; i < BatteryHistoricalLogUtil.MAX_ENTRIES + 10; i++) {
-            BatteryHistoricalLogUtil.writeLog(mContext, Action.RESET, "pkg" + i, "logs");
+        for (int i = 0; i < BatteryOptimizeLogUtils.MAX_ENTRIES + 10; i++) {
+            BatteryOptimizeLogUtils.writeLog(mContext, Action.RESET, "pkg" + i, "logs");
         }
-        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
+        BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
 
-        assertThat(mTestStringWriter.toString().split("RESET").length).isEqualTo(41);
+        assertActionCount("RESET", BatteryOptimizeLogUtils.MAX_ENTRIES);
+    }
+
+    private void assertActionCount(String token, int count) {
+        final String dumpResults = mTestStringWriter.toString();
+        assertThat(dumpResults.split(token).length).isEqualTo(count + 1);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java
new file mode 100644
index 0000000..66050a0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImplTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatterySettingsFeatureProviderImplTest {
+    private BatterySettingsFeatureProviderImpl mImpl;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mImpl = new BatterySettingsFeatureProviderImpl();
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
+    public void isManufactureDateAvailable_returnFalse() {
+        assertThat(mImpl.isManufactureDateAvailable(mContext, 1000L)).isFalse();
+    }
+
+    @Test
+    public void isFirstUseDateAvailable_returnFalse() {
+        assertThat(mImpl.isFirstUseDateAvailable(mContext, 1000L)).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java
index 1a43dbb..bf4e893 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImplTest.java
@@ -68,6 +68,15 @@
     }
 
     @Test
+    public void testIsBatteryTipsEnabled_returnFalse() {
+        assertThat(mPowerFeatureProvider.isBatteryTipsEnabled()).isFalse();
+    }
+
+    @Test
+    public void testIsBatteryTipsFeedbackEnabled_returnFalse() {
+        assertThat(mPowerFeatureProvider.isBatteryTipsFeedbackEnabled()).isFalse();
+    }
+    @Test
     public void testGetBatteryUsageListConsumePowerThreshold_return0() {
         assertThat(mPowerFeatureProvider.getBatteryUsageListConsumePowerThreshold()).isEqualTo(0.0);
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
index b444309..f6bc297 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java
@@ -31,6 +31,7 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+import android.os.BatteryManager;
 
 import androidx.preference.Preference;
 import androidx.test.core.app.ApplicationProvider;
@@ -146,6 +147,17 @@
     }
 
     @Test
+    public void getDashboardLabel_notChargingState_returnsCorrectLabel() {
+        mController.mPreference = new Preference(mContext);
+        BatteryInfo info = new BatteryInfo();
+        info.batteryStatus = BatteryManager.BATTERY_STATUS_NOT_CHARGING;
+        info.statusLabel = "expected returned label";
+
+        assertThat(mController.getDashboardLabel(mContext, info, true))
+                .isEqualTo(info.statusLabel);
+    }
+
+    @Test
     public void getSummary_batteryNotPresent_shouldShowWarningMessage() {
         mController.mIsBatteryPresent = false;
         assertThat(mController.getSummary())
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java
index 3513168..ecac4f9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTipTest.java
@@ -18,11 +18,11 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
-import android.view.View;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.View;
 
-import androidx.annotation.IdRes;
+import androidx.annotation.DrawableRes;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
@@ -45,7 +45,7 @@
 
     private static final String TITLE = "title";
     private static final String SUMMARY = "summary";
-    @IdRes
+    @DrawableRes
     private static final int ICON_ID = R.drawable.ic_fingerprint;
 
     private Context mContext;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
index a5f1ab3..9f6e4e3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
@@ -85,7 +85,7 @@
     @Test
     public void getIcon_showIcon() {
         assertThat(mIncompatibleChargerTip.getIconId())
-                .isEqualTo(R.drawable.ic_battery_alert_theme);
+                .isEqualTo(R.drawable.ic_battery_charger);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java
deleted file mode 100644
index 4b250a3..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java
+++ /dev/null
@@ -1,102 +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.fuelgauge.batteryusage;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-public final class AppUsageDataLoaderTest {
-    private Context mContext;
-    @Mock
-    private ContentResolver mMockContentResolver;
-    @Mock
-    private UserManager mUserManager;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application);
-        doReturn(mContext).when(mContext).getApplicationContext();
-        doReturn(mMockContentResolver).when(mContext).getContentResolver();
-        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
-        doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
-    }
-
-    @Test
-    public void loadAppUsageData_withData_insertFakeDataIntoProvider() {
-        final List<AppUsageEvent> AppUsageEventList = new ArrayList<>();
-        final AppUsageEvent appUsageEvent = AppUsageEvent.newBuilder().setUid(0).build();
-        AppUsageEventList.add(appUsageEvent);
-        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
-        AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> AppUsageEventList;
-
-        AppUsageDataLoader.loadAppUsageData(mContext);
-
-        verify(mMockContentResolver).bulkInsert(any(), any());
-        verify(mMockContentResolver).notifyChange(any(), any());
-    }
-
-    @Test
-    public void loadAppUsageData_nullAppUsageEvents_notInsertDataIntoProvider() {
-        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> null;
-
-        AppUsageDataLoader.loadAppUsageData(mContext);
-
-        verifyNoMoreInteractions(mMockContentResolver);
-    }
-
-    @Test
-    public void loadAppUsageData_nullUsageEventsList_notInsertDataIntoProvider() {
-        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
-        AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> null;
-
-        AppUsageDataLoader.loadAppUsageData(mContext);
-
-        verifyNoMoreInteractions(mMockContentResolver);
-    }
-
-    @Test
-    public void loadAppUsageData_emptyUsageEventsList_notInsertDataIntoProvider() {
-        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
-        AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> new ArrayList<>();
-
-        AppUsageDataLoader.loadAppUsageData(mContext);
-
-        verifyNoMoreInteractions(mMockContentResolver);
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
index e1c193c..a54d4c1 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static com.android.settings.fuelgauge.batteryusage.BatteryChartViewModel.SELECTED_INDEX_ALL;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -30,7 +32,6 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -39,6 +40,7 @@
 import android.os.LocaleList;
 import android.os.UserManager;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
 import android.widget.LinearLayout;
@@ -54,7 +56,6 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -112,6 +113,7 @@
         mBatteryChartPreferenceController.mPrefContext = mContext;
         mBatteryChartPreferenceController.mDailyChartView = mDailyChartView;
         mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView;
+        BatteryDiffEntry.clearCache();
         // Adds fake testing data.
         BatteryDiffEntry.sResourceCache.put(
                 "fakeBatteryDiffEntryKey",
@@ -144,7 +146,7 @@
         reset(mHourlyChartView);
         setupHourlyChartViewAnimationMock();
 
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6));
 
         verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE);
         // Ignore fast refresh ui from the data processor callback.
@@ -176,16 +178,18 @@
                 BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
                 mBatteryChartPreferenceController.mDailyChartLabelTextGenerator);
 
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60));
+        mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap());
 
         verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
         verify(mViewPropertyAnimator, atLeastOnce()).alpha(0f);
-        verify(mDailyChartView).setViewModel(expectedDailyViewModel);
+        verify(mDailyChartView, atLeastOnce()).setViewModel(expectedDailyViewModel);
 
         reset(mDailyChartView);
         reset(mHourlyChartView);
         setupHourlyChartViewAnimationMock();
         doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
+        doReturn(View.GONE).when(mHourlyChartView).getVisibility();
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
         mBatteryChartPreferenceController.refreshUi();
         verify(mDailyChartView).setVisibility(View.VISIBLE);
@@ -245,8 +249,7 @@
         setupHourlyChartViewAnimationMock();
         doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
         mBatteryChartPreferenceController.mDailyChartIndex = 2;
-        mBatteryChartPreferenceController.mHourlyChartIndex =
-                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL;
         mBatteryChartPreferenceController.refreshUi();
         verify(mDailyChartView).setVisibility(View.VISIBLE);
         verify(mViewPropertyAnimator, atLeastOnce()).alpha(1f);
@@ -272,13 +275,15 @@
 
     @Test
     public void refreshUi_normalCase_returnTrue() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6));
+        mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap());
         assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue();
     }
 
     @Test
     public void refreshUi_batteryIndexedMapIsNull_returnTrue() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(null);
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(null);
+        mBatteryChartPreferenceController.onBatteryUsageMapUpdate(getEmptyBatteryUsageMap());
         assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue();
     }
 
@@ -296,38 +301,34 @@
 
     @Test
     public void selectedSlotText_selectAllDaysAllHours_returnNull() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
-        mBatteryChartPreferenceController.mDailyChartIndex =
-                BatteryChartViewModel.SELECTED_INDEX_ALL;
-        mBatteryChartPreferenceController.mHourlyChartIndex =
-                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60));
+        mBatteryChartPreferenceController.mDailyChartIndex = SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL;
 
         assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
     }
 
     @Test
     public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6));
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
-        mBatteryChartPreferenceController.mHourlyChartIndex =
-                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL;
 
         assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
     }
 
     @Test
     public void selectedSlotText_selectADayAllHours_onlyDayText() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60));
         mBatteryChartPreferenceController.mDailyChartIndex = 1;
-        mBatteryChartPreferenceController.mHourlyChartIndex =
-                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL;
 
         assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday");
     }
 
     @Test
     public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6));
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
         mBatteryChartPreferenceController.mHourlyChartIndex = 2;
 
@@ -337,7 +338,7 @@
 
     @Test
     public void selectedSlotText_SelectADayAnHour_dayAndHourText() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(60));
         mBatteryChartPreferenceController.mDailyChartIndex = 1;
         mBatteryChartPreferenceController.mHourlyChartIndex = 8;
 
@@ -347,7 +348,7 @@
 
     @Test
     public void selectedSlotText_selectFirstSlot_withMinuteText() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6));
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
         mBatteryChartPreferenceController.mHourlyChartIndex = 0;
 
@@ -357,7 +358,7 @@
 
     @Test
     public void selectedSlotText_selectLastSlot_withNowText() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(6));
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
         mBatteryChartPreferenceController.mHourlyChartIndex = 3;
 
@@ -367,7 +368,7 @@
 
     @Test
     public void selectedSlotText_selectOnlySlot_withMinuteAndNowText() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(1));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(1));
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
         mBatteryChartPreferenceController.mHourlyChartIndex = 0;
 
@@ -388,7 +389,7 @@
         mBatteryChartPreferenceController.mHourlyChartIndex = -1;
 
         mBatteryChartPreferenceController.onCreate(bundle);
-        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25));
+        mBatteryChartPreferenceController.onBatteryLevelDataUpdate(createBatteryLevelData(25));
 
         assertThat(mBatteryChartPreferenceController.mDailyChartIndex)
                 .isEqualTo(expectedDailyIndex);
@@ -398,9 +399,7 @@
 
     @Test
     public void getTotalHours_getExpectedResult() {
-        Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createBatteryHistoryMap(60);
-        BatteryLevelData batteryLevelData =
-                DataProcessManager.getBatteryLevelData(mContext, null, batteryHistoryMap, null);
+        BatteryLevelData batteryLevelData = createBatteryLevelData(60);
 
         final int totalHour = BatteryChartPreferenceController.getTotalHours(batteryLevelData);
 
@@ -413,37 +412,26 @@
         return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
     }
 
-    private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap(
-            int numOfHours) {
-        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
-        for (int index = 0; index < numOfHours; index++) {
-            final ContentValues values = new ContentValues();
-            final DeviceBatteryState deviceBatteryState =
-                    DeviceBatteryState
-                            .newBuilder()
-                            .setBatteryLevel(100 - index)
-                            .build();
-            final BatteryInformation batteryInformation =
-                    BatteryInformation
-                            .newBuilder()
-                            .setDeviceBatteryState(deviceBatteryState)
-                            .setConsumePower(100 - index)
-                            .build();
-            values.put(BatteryHistEntry.KEY_BATTERY_INFORMATION,
-                    ConvertUtils.convertBatteryInformationToString(batteryInformation));
-            values.put(BatteryHistEntry.KEY_PACKAGE_NAME, "package" + index);
-            final BatteryHistEntry entry = new BatteryHistEntry(values);
-            final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
-            entryMap.put("fake_entry_key" + index, entry);
-            long timestamp = generateTimestamp(index);
+    private static BatteryLevelData createBatteryLevelData(int numOfHours) {
+        Map<Long, Integer> batteryLevelMap = new ArrayMap<>();
+        for (int index = 0; index < numOfHours; index += 2) {
+            final Integer level = 100 - index;
+            Long timestamp = generateTimestamp(index);
             if (index == 0) {
                 timestamp += DateUtils.MINUTE_IN_MILLIS;
+                index--;
             }
-            batteryHistoryMap.put(timestamp, entryMap);
+            batteryLevelMap.put(timestamp, level);
         }
-        DataProcessor.sTestCurrentTimeMillis =
-                generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS * 2;
-        return batteryHistoryMap;
+        long current = generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS * 2;
+        batteryLevelMap.put(current, 66);
+        DataProcessor.sTestCurrentTimeMillis = current;
+        return new BatteryLevelData(batteryLevelMap);
+    }
+
+    private static Map<Integer, Map<Integer, BatteryDiffData>> getEmptyBatteryUsageMap() {
+        return Map.of(SELECTED_INDEX_ALL, Map.of(SELECTED_INDEX_ALL, new BatteryDiffData(
+                null, 0, 0, 0, 0, 0, List.of(), List.of(), Set.of(), Set.of(), false)));
     }
 
     private BatteryChartPreferenceController createController() {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java
index 27539a5..d4bae29 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffDataTest.java
@@ -147,6 +147,14 @@
                 /*foregroundUsageTimeInMs=*/ 0L,  /*backgroundUsageTimeInMs=*/ 0L, isHidden);
         return new BatteryDiffEntry(
                 context,
+                batteryHistEntry.mUid,
+                batteryHistEntry.mUserId,
+                batteryHistEntry.getKey(),
+                batteryHistEntry.mIsHidden,
+                batteryHistEntry.mDrainType,
+                batteryHistEntry.mPackageName,
+                batteryHistEntry.mAppLabel,
+                batteryHistEntry.mConsumerType,
                 /*foregroundUsageTimeInMs=*/ 0,
                 /*backgroundUsageTimeInMs=*/ 0,
                 /*screenOnTimeInMs=*/ 0,
@@ -154,8 +162,7 @@
                 /*foregroundUsageConsumePower=*/ 0,
                 /*foregroundServiceUsageConsumePower=*/ 0,
                 /*backgroundUsageConsumePower=*/ 0,
-                /*cachedUsageConsumePower=*/ 0,
-                batteryHistEntry);
+                /*cachedUsageConsumePower=*/ 0);
     }
 
     private static BatteryHistEntry createBatteryHistEntry(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
index 655f1e4..9bb4b73 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntryTest.java
@@ -95,6 +95,14 @@
         final BatteryDiffEntry entry =
                 new BatteryDiffEntry(
                         mContext,
+                        /*uid=*/ 0,
+                        /*userId=*/ 0,
+                        /*key=*/ "key",
+                        /*isHidden=*/ false,
+                        /*componentId=*/ -1,
+                        /*legacyPackageName=*/ null,
+                        /*legacyLabel=*/ null,
+                        /*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
                         /*foregroundUsageTimeInMs=*/ 10001L,
                         /*backgroundUsageTimeInMs=*/ 20002L,
                         /*screenOnTimeInMs=*/ 30003L,
@@ -102,8 +110,7 @@
                         /*foregroundUsageConsumePower=*/ 10.0,
                         /*foregroundServiceUsageConsumePower=*/ 10.0,
                         /*backgroundUsageConsumePower=*/ 1.0,
-                        /*cachedUsageConsumePower=*/ 1.0,
-                        /*batteryHistEntry=*/ null);
+                        /*cachedUsageConsumePower=*/ 1.0);
         entry.setTotalConsumePower(100.0);
 
         assertThat(entry.getPercentage()).isEqualTo(22.0);
@@ -114,6 +121,14 @@
         final BatteryDiffEntry entry =
                 new BatteryDiffEntry(
                         mContext,
+                        /*uid=*/ 0,
+                        /*userId=*/ 0,
+                        /*key=*/ "key",
+                        /*isHidden=*/ false,
+                        /*componentId=*/ -1,
+                        /*legacyPackageName=*/ null,
+                        /*legacyLabel=*/ null,
+                        /*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
                         /*foregroundUsageTimeInMs=*/ 10001L,
                         /*backgroundUsageTimeInMs=*/ 20002L,
                         /*screenOnTimeInMs=*/ 30003L,
@@ -121,8 +136,7 @@
                         /*foregroundUsageConsumePower=*/ 10.0,
                         /*foregroundServiceUsageConsumePower=*/ 10.0,
                         /*backgroundUsageConsumePower=*/ 1.0,
-                        /*cachedUsageConsumePower=*/ 1.0,
-                        /*batteryHistEntry=*/ null);
+                        /*cachedUsageConsumePower=*/ 1.0);
         entry.setTotalConsumePower(0);
 
         assertThat(entry.getPercentage()).isEqualTo(0);
@@ -133,7 +147,24 @@
         final List<BatteryDiffEntry> entryList = new ArrayList<>();
         // Generates fake testing data.
         BatteryDiffEntry systemAppsBatteryDiffEntry =
-                new BatteryDiffEntry.SystemAppsBatteryDiffEntry(mContext);
+                new BatteryDiffEntry(
+                        mContext,
+                        /*uid=*/ 0,
+                        /*userId=*/ 0,
+                        /*key=*/ BatteryDiffEntry.SYSTEM_APPS_KEY,
+                        /*isHidden=*/ false,
+                        /*componentId=*/ -1,
+                        /*legacyPackageName=*/ null,
+                        /*legacyLabel=*/ BatteryDiffEntry.SYSTEM_APPS_KEY,
+                        /*consumerType*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
+                        /*foregroundUsageTimeInMs=*/ 0,
+                        /*backgroundUsageTimeInMs=*/ 0,
+                        /*screenOnTimeInMs=*/ 0,
+                        /*consumePower=*/ 0,
+                        /*foregroundUsageConsumePower=*/ 0,
+                        /*foregroundServiceUsageConsumePower=*/ 0,
+                        /*backgroundUsageConsumePower=*/ 0,
+                        /*cachedUsageConsumePower=*/ 0);
         systemAppsBatteryDiffEntry.mConsumePower = 16;
         systemAppsBatteryDiffEntry.setTotalConsumePower(100);
         entryList.add(systemAppsBatteryDiffEntry);
@@ -448,17 +479,16 @@
 
     private BatteryDiffEntry createBatteryDiffEntry(
             int consumerType, long uid, boolean isHidden) {
-        final ContentValues values = getContentValuesWithType(consumerType);
-        final BatteryInformation batteryInformation =
-                BatteryInformation
-                        .newBuilder()
-                        .setIsHidden(isHidden)
-                        .build();
-        values.put(BatteryHistEntry.KEY_BATTERY_INFORMATION,
-                ConvertUtils.convertBatteryInformationToString(batteryInformation));
-        values.put(BatteryHistEntry.KEY_UID, uid);
         return new BatteryDiffEntry(
                 mContext,
+                /*uid=*/ uid,
+                /*userId=*/ 0,
+                /*key=*/ "key",
+                /*isHidden=*/ isHidden,
+                /*componentId=*/ -1,
+                /*legacyPackageName=*/ null,
+                /*legacyLabel=*/ null,
+                /*consumerType*/ consumerType,
                 /*foregroundUsageTimeInMs=*/ 0,
                 /*backgroundUsageTimeInMs=*/ 0,
                 /*screenOnTimeInMs=*/ 0,
@@ -466,14 +496,21 @@
                 /*foregroundUsageConsumePower=*/ 0,
                 /*foregroundServiceUsageConsumePower=*/ 0,
                 /*backgroundUsageConsumePower=*/ 0,
-                /*cachedUsageConsumePower=*/ 0,
-                new BatteryHistEntry(values));
+                /*cachedUsageConsumePower=*/ 0);
     }
 
     private BatteryDiffEntry createBatteryDiffEntry(
             double consumePower, BatteryHistEntry batteryHistEntry) {
         final BatteryDiffEntry entry = new BatteryDiffEntry(
                 mContext,
+                batteryHistEntry.mUid,
+                batteryHistEntry.mUserId,
+                batteryHistEntry.getKey(),
+                batteryHistEntry.mIsHidden,
+                batteryHistEntry.mDrainType,
+                batteryHistEntry.mPackageName,
+                batteryHistEntry.mAppLabel,
+                batteryHistEntry.mConsumerType,
                 /*foregroundUsageTimeInMs=*/ 0,
                 /*backgroundUsageTimeInMs=*/ 0,
                 /*screenOnTimeInMs=*/ 0,
@@ -481,8 +518,7 @@
                 /*foregroundUsageConsumePower=*/ 0,
                 /*foregroundServiceUsageConsumePower=*/ 0,
                 /*backgroundUsageConsumePower=*/ 0,
-                /*cachedUsageConsumePower=*/ 0,
-                batteryHistEntry);
+                /*cachedUsageConsumePower=*/ 0);
         entry.setTotalConsumePower(100.0);
         return entry;
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java
index dec5d7d..108d6e2 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java
@@ -40,6 +40,7 @@
 import com.android.settings.fuelgauge.batteryusage.BatteryEntry.NameAndIcon;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,8 +51,6 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-import java.util.Locale;
-
 @RunWith(RobolectricTestRunner.class)
 public class BatteryEntryTest {
 
@@ -232,17 +231,7 @@
         assertThat(entry.getTimeInBackgroundMs()).isEqualTo(0);
     }
 
-    @Test
-    public void testUidCache_switchLocale_shouldCleanCache() {
-        Locale.setDefault(new Locale("en_US"));
-        BatteryEntry.sUidCache.put(Integer.toString(APP_UID), null);
-        assertThat(BatteryEntry.sUidCache).isNotEmpty();
-
-        Locale.setDefault(new Locale("zh_TW"));
-        createBatteryEntryForApp(null, null, HIGH_DRAIN_PACKAGE);
-        assertThat(BatteryEntry.sUidCache).isEmpty(); // check if cache is clear
-    }
-
+    @Ignore
     @Test
     public void getKey_UidBatteryConsumer() {
         final BatteryEntry entry = createBatteryEntryForApp(null, null, null);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
index 9667760..609f2fc 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.settings.fuelgauge.batteryusage;
 
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isSystemConsumer;
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUidConsumer;
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserConsumer;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.when;
@@ -147,32 +151,32 @@
 
     @Test
     public void testIsAppEntry_returnExpectedResult() {
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).isAppEntry())
-                .isFalse();
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).isAppEntry())
-                .isFalse();
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).isAppEntry())
-                .isTrue();
+        assertThat(isUidConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).mConsumerType)).isFalse();
+        assertThat(isUidConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).mConsumerType)).isFalse();
+        assertThat(isUidConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).mConsumerType)).isTrue();
     }
 
     @Test
     public void testIsUserEntry_returnExpectedResult() {
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).isUserEntry())
-                .isFalse();
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).isUserEntry())
-                .isTrue();
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).isUserEntry())
-                .isFalse();
+        assertThat(isUserConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).mConsumerType)).isFalse();
+        assertThat(isUserConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).mConsumerType)).isTrue();
+        assertThat(isUserConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).mConsumerType)).isFalse();
     }
 
     @Test
     public void testIsSystemEntry_returnExpectedResult() {
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).isSystemEntry())
-                .isTrue();
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).isSystemEntry())
-                .isFalse();
-        assertThat(createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).isSystemEntry())
-                .isFalse();
+        assertThat(isSystemConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY).mConsumerType)).isTrue();
+        assertThat(isSystemConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_USER_BATTERY).mConsumerType)).isFalse();
+        assertThat(isSystemConsumer(
+                createEntry(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).mConsumerType)).isFalse();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java
index e14ead5..9155c66 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreferenceTest.java
@@ -27,7 +27,6 @@
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -42,8 +41,6 @@
     @Mock
     private PreferenceViewHolder mViewHolder;
     @Mock
-    private BatteryInfo mBatteryInfo;
-    @Mock
     private TextView mTextView;
     @Mock
     private BatteryChartView mDailyChartView;
@@ -59,7 +56,6 @@
                 LayoutInflater.from(context).inflate(R.layout.battery_chart_graph, null);
 
         mBatteryHistoryPreference = new BatteryHistoryPreference(context, null);
-        mBatteryHistoryPreference.mBatteryInfo = mBatteryInfo;
         mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(itemView));
         when(mViewHolder.findViewById(R.id.daily_battery_chart)).thenReturn(mDailyChartView);
         when(mViewHolder.findViewById(R.id.hourly_battery_chart)).thenReturn(mHourlyChartView);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java
new file mode 100644
index 0000000..13d60bb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelDataTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatteryLevelDataTest {
+
+    @Before
+    public void setUp() {
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
+    }
+
+    @Test
+    public void getDailyTimestamps_allDataInOneHour_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640970006000L, // 2022-01-01 01:00:06
+                1640973608000L  // 2022-01-01 01:00:08
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1640970006000L, // 2022-01-01 01:00:06
+                1640973608000L  // 2022-01-01 01:00:08
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_OneHourDataPerDay_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1641049200000L, // 2022-01-01 23:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L  // 2022-01-02 01:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1641049200000L, // 2022-01-01 23:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L  // 2022-01-02 01:00:00
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_OneDayData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640966400000L, // 2022-01-01 00:00:00
+                1640970000000L, // 2022-01-01 01:00:00
+                1640973600000L, // 2022-01-01 02:00:00
+                1640977200000L, // 2022-01-01 03:00:00
+                1640980800000L  // 2022-01-01 04:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1640966400000L, // 2022-01-01 00:00:00
+                1640980800000L  // 2022-01-01 04:00:00
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_MultipleDaysData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1641045600000L, // 2022-01-01 22:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+                1641160800000L, // 2022-01-03 06:00:00
+                1641232800000L  // 2022-01-04 02:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1641045600000L, // 2022-01-01 22:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641139200000L, // 2022-01-03 00:00:00
+                1641225600000L, // 2022-01-04 00:00:00
+                1641232800000L  // 2022-01-04 02:00:00
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_FirstDayOneHourData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1641049200000L, // 2022-01-01 23:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+                1641160800000L, // 2022-01-03 06:00:00
+                1641254400000L  // 2022-01-04 08:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1641049200000L, // 2022-01-01 23:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641139200000L, // 2022-01-03 00:00:00
+                1641225600000L, // 2022-01-04 00:00:00
+                1641254400000L  // 2022-01-04 08:00:00
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_LastDayNoData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640988000000L, // 2022-01-01 06:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+                1641160800000L, // 2022-01-03 06:00:00
+                1641225600000L  // 2022-01-04 00:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1640988000000L, // 2022-01-01 06:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641139200000L, // 2022-01-03 00:00:00
+                1641225600000L  // 2022-01-04 00:00:00
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_LastDayOneHourData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640988000000L, // 2022-01-01 06:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+                1641160800000L, // 2022-01-03 06:00:00
+                1641229200000L  // 2022-01-04 01:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1640988000000L, // 2022-01-01 06:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641139200000L, // 2022-01-03 00:00:00
+                1641225600000L, // 2022-01-04 00:00:00
+                1641229200000L  // 2022-01-04 01:00:00
+        );
+        assertThat(BatteryLevelData.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void combine_normalFlow_returnExpectedResult() {
+        final BatteryLevelData batteryLevelData =
+                new BatteryLevelData(Map.of(1691596800000L, 90, 1691604000000L, 80));
+        final List<BatteryEvent> batteryLevelRecordEvents = List.of(
+                BatteryEvent.newBuilder().setTimestamp(1691586000166L).setBatteryLevel(100)
+                        .setType(BatteryEventType.FULL_CHARGED).build(),
+                BatteryEvent.newBuilder().setTimestamp(1691589600000L).setBatteryLevel(98)
+                        .setType(BatteryEventType.EVEN_HOUR).build());
+
+        BatteryLevelData result =
+                BatteryLevelData.combine(batteryLevelData, batteryLevelRecordEvents);
+
+        assertThat(result.getDailyBatteryLevels().getTimestamps())
+                .isEqualTo(List.of(1691586000166L, 1691596800000L, 1691604000000L));
+        assertThat(result.getDailyBatteryLevels().getLevels())
+                .isEqualTo(List.of(100, 90, 80));
+        assertThat(result.getHourlyBatteryLevelsPerDay())
+                .hasSize(2);
+        assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getTimestamps())
+                .isEqualTo(List.of(1691586000166L, 1691589600000L, 1691596800000L));
+        assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getLevels())
+                .isEqualTo(List.of(100, 98, 90));
+        assertThat(result.getHourlyBatteryLevelsPerDay().get(1).getTimestamps())
+                .isEqualTo(List.of(1691596800000L, 1691604000000L));
+        assertThat(result.getHourlyBatteryLevelsPerDay().get(1).getLevels())
+                .isEqualTo(List.of(90, 80));
+    }
+
+    @Test
+    public void combine_existingBatteryLevelDataIsNull_returnExpectedResult() {
+        final List<BatteryEvent> batteryLevelRecordEvents = List.of(
+                BatteryEvent.newBuilder().setTimestamp(1691586000166L).setBatteryLevel(100)
+                        .setType(BatteryEventType.FULL_CHARGED).build(),
+                BatteryEvent.newBuilder().setTimestamp(1691589600000L).setBatteryLevel(98)
+                        .setType(BatteryEventType.EVEN_HOUR).build());
+
+        BatteryLevelData result =
+                BatteryLevelData.combine(null, batteryLevelRecordEvents);
+
+        assertThat(result.getHourlyBatteryLevelsPerDay())
+                .hasSize(1);
+        assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getTimestamps())
+                .isEqualTo(List.of(1691586000166L, 1691589600000L));
+        assertThat(result.getHourlyBatteryLevelsPerDay().get(0).getLevels())
+                .isEqualTo(List.of(100, 98));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
new file mode 100644
index 0000000..ed2b315
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+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.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.display.AutoBrightnessSettings;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.testutils.BatteryTestUtils;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+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;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryTipsCardPreferenceTest {
+
+    private Context mContext;
+    private BatteryTipsCardPreference mBatteryTipsCardPreference;
+    private BatteryTipsController mBatteryTipsController;
+    @Mock
+    private View mFakeView;
+    @Mock
+    private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mBatteryTipsCardPreference = new BatteryTipsCardPreference(mContext, /*attrs=*/ null);
+        mBatteryTipsController = new BatteryTipsController(mContext);
+        mBatteryTipsController.mCardPreference = mBatteryTipsCardPreference;
+        mBatteryTipsController.mPowerUsageFeatureProvider = mPowerUsageFeatureProvider;
+    }
+
+    @Test
+    public void constructor_returnExpectedResult() {
+        assertThat(mBatteryTipsCardPreference.getLayoutResource()).isEqualTo(
+                R.layout.battery_tips_card);
+    }
+    @Test
+    public void onClick_actionBtn_getAdaptiveBrightnessLauncher() {
+        final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        PowerAnomalyEvent adaptiveBrightnessAnomaly =
+                BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
+        when(mPowerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
+        when(mFakeView.getId()).thenReturn(R.id.main_button);
+        doNothing().when(mContext).startActivity(captor.capture());
+
+        mBatteryTipsController.handleBatteryTipsCardUpdated(adaptiveBrightnessAnomaly);
+        mBatteryTipsCardPreference.onClick(mFakeView);
+
+        verify(mContext).startActivity(any(Intent.class));
+        final Intent intent = captor.getValue();
+        assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+                .isEqualTo(AutoBrightnessSettings.class.getName());
+        assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, -1))
+                .isEqualTo(SettingsEnums.SETTINGS_AUTO_BRIGHTNESS);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
new file mode 100644
index 0000000..ffb200d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import static org.mockito.Mockito.doReturn;
+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.res.Resources;
+import android.os.LocaleList;
+
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.testutils.BatteryTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryTipsControllerTest {
+
+    private Context mContext;
+    private BatteryTipsController mBatteryTipsController;
+
+    @Mock
+    private BatteryTipsCardPreference mBatteryTipsCardPreference;
+
+    @Mock
+    private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Locale.setDefault(new Locale("en_US"));
+        org.robolectric.shadows.ShadowSettings.set24HourTimeFormat(false);
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        mContext = spy(RuntimeEnvironment.application);
+        final Resources resources = spy(mContext.getResources());
+        resources.getConfiguration().setLocales(new LocaleList(new Locale("en_US")));
+        doReturn(resources).when(mContext).getResources();
+        mBatteryTipsController = new BatteryTipsController(mContext);
+        mBatteryTipsController.mCardPreference = mBatteryTipsCardPreference;
+        mBatteryTipsController.mPowerUsageFeatureProvider = mPowerUsageFeatureProvider;
+    }
+
+    @Test
+    public void handleBatteryTipsCardUpdated_null_hidePreference() {
+        mBatteryTipsController.handleBatteryTipsCardUpdated(/* powerAnomalyEvents= */ null);
+
+        verify(mBatteryTipsCardPreference).setVisible(false);
+    }
+
+    @Test
+    public void handleBatteryTipsCardUpdated_adaptiveBrightnessAnomaly_showAnomaly() {
+        PowerAnomalyEvent event = BatteryTestUtils.createAdaptiveBrightnessAnomalyEvent();
+        when(mPowerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
+
+        mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+
+        // Check pre-defined string
+        verify(mBatteryTipsCardPreference).setTitle(
+                "Turn on adaptive brightness to extend battery life");
+        verify(mBatteryTipsCardPreference).setMainButtonLabel("View Settings");
+        verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it");
+        // Check proto info
+        verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo(
+                "com.android.settings.display.AutoBrightnessSettings",
+                1381);
+        verify(mBatteryTipsCardPreference).setVisible(true);
+    }
+
+    @Test
+    public void handleBatteryTipsCardUpdated_screenTimeoutAnomaly_showAnomaly() {
+        PowerAnomalyEvent event = BatteryTestUtils.createScreenTimeoutAnomalyEvent();
+        when(mPowerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
+
+        mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+
+        verify(mBatteryTipsCardPreference).setTitle("Reduce screen timeout to extend battery life");
+        verify(mBatteryTipsCardPreference).setMainButtonLabel("View Settings");
+        verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it");
+        verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo(
+                "com.android.settings.display.ScreenTimeoutSettings",
+                1852);
+        verify(mBatteryTipsCardPreference).setVisible(true);
+    }
+    @Test
+    public void handleBatteryTipsCardUpdated_screenTimeoutAnomalyHasTitle_showAnomaly() {
+        PowerAnomalyEvent event = BatteryTestUtils.createScreenTimeoutAnomalyEvent();
+        String testTitle = "TestTitle";
+        event = event.toBuilder()
+                .setWarningBannerInfo(
+                        event.getWarningBannerInfo().toBuilder()
+                                .setTitleString(testTitle)
+                                .build())
+                .build();
+        when(mPowerUsageFeatureProvider.isBatteryTipsEnabled()).thenReturn(true);
+
+        mBatteryTipsController.handleBatteryTipsCardUpdated(event);
+
+        verify(mBatteryTipsCardPreference).setTitle(testTitle);
+        verify(mBatteryTipsCardPreference).setMainButtonLabel("View Settings");
+        verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it");
+        verify(mBatteryTipsCardPreference).setMainButtonLauncherInfo(
+                "com.android.settings.display.ScreenTimeoutSettings",
+                1852);
+        verify(mBatteryTipsCardPreference).setVisible(true);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index 3a9ce2b..d89c06b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -96,6 +96,14 @@
         mBatteryUsageBreakdownController.mAppListPreferenceGroup = mAppListPreferenceGroup;
         mBatteryDiffEntry = new BatteryDiffEntry(
                 mContext,
+                /*uid=*/ 0L,
+                /*userId=*/ 0L,
+                /*key=*/ "key",
+                /*isHidden=*/ false,
+                /*componentId=*/ -1,
+                /*legacyPackageName=*/ null,
+                /*legacyLabel=*/ null,
+                /*consumerType=*/ ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
                 /*foregroundUsageTimeInMs=*/ 1,
                 /*backgroundUsageTimeInMs=*/ 2,
                 /*screenOnTimeInMs=*/ 0,
@@ -103,13 +111,14 @@
                 /*foregroundUsageConsumePower=*/ 0,
                 /*foregroundServiceUsageConsumePower=*/ 1,
                 /*backgroundUsageConsumePower=*/ 2,
-                /*cachedUsageConsumePower=*/ 0,
-                mBatteryHistEntry);
+                /*cachedUsageConsumePower=*/ 0);
         mBatteryDiffEntry = spy(mBatteryDiffEntry);
         mBatteryUsageBreakdownController.mBatteryDiffData =
-                new BatteryDiffData(mContext, /* screenOnTime= */ 0L,
-                        Arrays.asList(mBatteryDiffEntry), Arrays.asList(), Set.of(), Set.of(),
-                        /* isAccumulated= */ false);
+                new BatteryDiffData(mContext, /* startTimestamp= */ 0L, /* endTimestamp= */ 0L,
+                        /* startBatteryLevel= */ 0, /* endBatteryLevel= */ 0,
+                        /* screenOnTime= */ 0L, Arrays.asList(mBatteryDiffEntry), Arrays.asList(),
+                        Set.of(), Set.of(), /* isAccumulated= */ false);
+        BatteryDiffEntry.clearCache();
         // Adds fake testing data.
         BatteryDiffEntry.sResourceCache.put(
                 "fakeBatteryDiffEntryKey",
@@ -140,7 +149,7 @@
         doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
         doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
         doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel();
-        doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
+        doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
         doReturn(null).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
         doReturn(false).when(mBatteryDiffEntry).validForRestriction();
 
@@ -168,7 +177,7 @@
         doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
         doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
         doReturn(appLabel).when(mBatteryDiffEntry).getAppLabel();
-        doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
+        doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
         doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
 
         mBatteryUsageBreakdownController.addAllPreferences();
@@ -197,7 +206,7 @@
     public void removeAndCacheAllUnusedPreferences_keepPref_KeepAllPreference() {
         doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
         doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
-        doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
+        doReturn(PREF_KEY).when(mBatteryDiffEntry).getKey();
         doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
         doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
         // Ensures the testing data is correct.
@@ -222,7 +231,7 @@
 
     @Test
     public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
-        doReturn(false).when(mBatteryHistEntry).isAppEntry();
+        mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY;
         doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
 
         assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick(
@@ -238,7 +247,7 @@
 
     @Test
     public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
-        doReturn(true).when(mBatteryHistEntry).isAppEntry();
+        mBatteryDiffEntry.mConsumerType = ConvertUtils.CONSUMER_TYPE_UID_BATTERY;
         doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
 
         assertThat(mBatteryUsageBreakdownController.handlePreferenceTreeClick(
@@ -394,10 +403,23 @@
         contentValues.put(BatteryHistEntry.KEY_USER_ID, Integer.valueOf(1001));
         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(contentValues);
         return new BatteryDiffEntry(
-                mContext, foregroundUsageTimeInMs, backgroundUsageTimeInMs, screenOnTimeInMs,
-                /*consumePower=*/ 0, /*foregroundUsageConsumePower=*/ 0,
-                /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0,
-                /*cachedUsageConsumePower=*/ 0, batteryHistEntry);
+                mContext,
+                batteryHistEntry.mUid,
+                batteryHistEntry.mUserId,
+                batteryHistEntry.getKey(),
+                batteryHistEntry.mIsHidden,
+                batteryHistEntry.mDrainType,
+                batteryHistEntry.mPackageName,
+                batteryHistEntry.mAppLabel,
+                batteryHistEntry.mConsumerType,
+                foregroundUsageTimeInMs,
+                backgroundUsageTimeInMs,
+                screenOnTimeInMs,
+                /*consumePower=*/ 0,
+                /*foregroundUsageConsumePower=*/ 0,
+                /*foregroundServiceUsageConsumePower=*/ 0,
+                /*backgroundUsageConsumePower=*/ 0,
+                /*cachedUsageConsumePower=*/ 0);
     }
 
     private BatteryUsageBreakdownController createController() {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
index 05a6f2b..999a921 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -20,11 +20,9 @@
 
 import static org.junit.Assert.assertThrows;
 
-import android.app.Application;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
 
@@ -34,6 +32,7 @@
 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
 import com.android.settings.testutils.BatteryTestUtils;
 import com.android.settings.testutils.FakeClock;
 
@@ -41,12 +40,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.Shadows;
 
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /** Tests for {@link BatteryUsageContentProvider}. */
@@ -127,10 +124,28 @@
     }
 
     @Test
+    public void query_getLastFullChargeTimestamp_returnsExpectedResult() throws Exception {
+        mProvider.onCreate();
+        ContentValues values = new ContentValues();
+        values.put(BatteryEventEntity.KEY_TIMESTAMP, 10001L);
+        values.put(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE,
+                BatteryEventType.FULL_CHARGED.getNumber());
+        values.put(BatteryEventEntity.KEY_BATTERY_LEVEL, 100);
+        mProvider.insert(DatabaseUtils.BATTERY_EVENT_URI, values);
+
+        final Cursor cursor = getCursorOfLastFullChargeTimestamp();
+
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        final long lastFullChargeTimestamp = cursor.getLong(0);
+        assertThat(lastFullChargeTimestamp).isEqualTo(10001L);
+    }
+
+    @Test
     public void query_batteryState_returnsExpectedResult() throws Exception {
         mProvider.onCreate();
         final Duration currentTime = Duration.ofHours(52);
-        final long expiredTimeCutoff = currentTime.toMillis() - 3;
+        final long expiredTimeCutoff = currentTime.toMillis() - 8;
 
         final Cursor cursor = insertBatteryState(currentTime, Long.toString(expiredTimeCutoff));
 
@@ -150,19 +165,13 @@
         final String actualPackageName3 = cursor.getString(packageNameIndex);
         assertThat(actualPackageName3).isEqualTo(PACKAGE_NAME3);
         cursor.close();
-        // Verifies the broadcast intent.
-        TimeUnit.SECONDS.sleep(1);
-        final List<Intent> intents = Shadows.shadowOf((Application) mContext).getBroadcastIntents();
-        assertThat(intents).hasSize(1);
-        assertThat(intents.get(0).getAction()).isEqualTo(
-                BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK);
     }
 
     @Test
     public void query_batteryStateTimestamp_returnsExpectedResult() throws Exception {
         mProvider.onCreate();
         final Duration currentTime = Duration.ofHours(52);
-        final long expiredTimeCutoff = currentTime.toMillis() - 1;
+        final long expiredTimeCutoff = currentTime.toMillis() - 2;
 
         final Cursor cursor = insertBatteryState(currentTime, Long.toString(expiredTimeCutoff));
 
@@ -178,12 +187,25 @@
         final String actualPackageName2 = cursor.getString(packageNameIndex);
         assertThat(actualPackageName2).isEqualTo(PACKAGE_NAME3);
         cursor.close();
-        // Verifies the broadcast intent.
-        TimeUnit.SECONDS.sleep(1);
-        final List<Intent> intents = Shadows.shadowOf((Application) mContext).getBroadcastIntents();
-        assertThat(intents).hasSize(1);
-        assertThat(intents.get(0).getAction()).isEqualTo(
-                BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK);
+    }
+
+    @Test
+    public void query_getBatteryStateLatestTimestamp_returnsExpectedResult() throws Exception {
+        mProvider.onCreate();
+        final Duration currentTime = Duration.ofHours(52);
+        insertBatteryState(currentTime, Long.toString(currentTime.toMillis()));
+
+        final Cursor cursor1 = getCursorOfBatteryStateLatestTimestamp(currentTime.toMillis() - 5);
+        assertThat(cursor1.getCount()).isEqualTo(1);
+        cursor1.moveToFirst();
+        final long latestTimestamp1 = cursor1.getLong(0);
+        assertThat(latestTimestamp1).isEqualTo(currentTime.toMillis() - 6);
+
+        final Cursor cursor2 = getCursorOfBatteryStateLatestTimestamp(currentTime.toMillis() - 2);
+        assertThat(cursor2.getCount()).isEqualTo(1);
+        cursor2.moveToFirst();
+        final long latestTimestamp2 = cursor2.getLong(0);
+        assertThat(latestTimestamp2).isEqualTo(currentTime.toMillis() - 2);
     }
 
     @Test
@@ -355,7 +377,7 @@
     }
 
     @Test
-    public void insert_batteryEvent_returnsExpectedResult() {
+    public void insertAndQuery_batteryEvent_returnsExpectedResult() {
         mProvider.onCreate();
         ContentValues values = new ContentValues();
         values.put(BatteryEventEntity.KEY_TIMESTAMP, 10001L);
@@ -366,7 +388,7 @@
         final Uri uri = mProvider.insert(DatabaseUtils.BATTERY_EVENT_URI, values);
 
         assertThat(uri).isEqualTo(DatabaseUtils.BATTERY_EVENT_URI);
-        // Verifies the AppUsageEventEntity content.
+        // Verifies the BatteryEventEntity content.
         final List<BatteryEventEntity> entities =
                 BatteryStateDatabase.getInstance(mContext).batteryEventDao().getAll();
         assertThat(entities).hasSize(1);
@@ -374,6 +396,50 @@
         assertThat(entities.get(0).batteryEventType).isEqualTo(
                 BatteryEventType.POWER_CONNECTED.getNumber());
         assertThat(entities.get(0).batteryLevel).isEqualTo(66);
+
+        final Cursor cursor1 = getCursorOfBatteryEvents(
+                0L, List.of(BatteryEventType.POWER_CONNECTED.getNumber()));
+        assertThat(cursor1.getCount()).isEqualTo(1);
+        cursor1.moveToFirst();
+        assertThat(cursor1.getLong(cursor1.getColumnIndex(BatteryEventEntity.KEY_TIMESTAMP)))
+                .isEqualTo(10001L);
+        assertThat(
+                cursor1.getInt(cursor1.getColumnIndex(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE)))
+                .isEqualTo(BatteryEventType.POWER_CONNECTED.getNumber());
+        assertThat(cursor1.getInt(cursor1.getColumnIndex(BatteryEventEntity.KEY_BATTERY_LEVEL)))
+                .isEqualTo(66);
+
+        final Cursor cursor2 = getCursorOfBatteryEvents(
+                0L, List.of(BatteryEventType.POWER_DISCONNECTED.getNumber()));
+        assertThat(cursor2.getCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void insertAndQuery_batteryUsageSlot_returnsExpectedResult() {
+        mProvider.onCreate();
+        ContentValues values = new ContentValues();
+        values.put(BatteryUsageSlotEntity.KEY_TIMESTAMP, 10001L);
+        values.put(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT, "TEST_STRING");
+
+        final Uri uri = mProvider.insert(DatabaseUtils.BATTERY_USAGE_SLOT_URI, values);
+        // Verifies the BatteryUsageSlotEntity content.
+        assertThat(uri).isEqualTo(DatabaseUtils.BATTERY_USAGE_SLOT_URI);
+        final List<BatteryUsageSlotEntity> entities =
+                BatteryStateDatabase.getInstance(mContext).batteryUsageSlotDao().getAll();
+        assertThat(entities).hasSize(1);
+        assertThat(entities.get(0).timestamp).isEqualTo(10001L);
+        assertThat(entities.get(0).batteryUsageSlot).isEqualTo("TEST_STRING");
+
+        final Cursor cursor1 = getCursorOfBatteryUsageSlots(10001L);
+        assertThat(cursor1.getCount()).isEqualTo(1);
+        cursor1.moveToFirst();
+        assertThat(cursor1.getLong(cursor1.getColumnIndex(BatteryUsageSlotEntity.KEY_TIMESTAMP)))
+                .isEqualTo(10001L);
+        assertThat(cursor1.getString(cursor1.getColumnIndex(
+                BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT))).isEqualTo("TEST_STRING");
+
+        final Cursor cursor2 = getCursorOfBatteryUsageSlots(10002L);
+        assertThat(cursor2.getCount()).isEqualTo(0);
     }
 
     @Test
@@ -404,10 +470,10 @@
         final long currentTimestamp = currentTime.toMillis();
         // Inserts some valid testing data.
         BatteryTestUtils.insertDataToBatteryStateTable(
-                mContext, currentTimestamp - 2, PACKAGE_NAME1,
+                mContext, currentTimestamp - 6, PACKAGE_NAME1,
                 /*isFullChargeStart=*/ true);
         BatteryTestUtils.insertDataToBatteryStateTable(
-                mContext, currentTimestamp - 1, PACKAGE_NAME2);
+                mContext, currentTimestamp - 2, PACKAGE_NAME2);
         BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, currentTimestamp, PACKAGE_NAME3);
 
@@ -420,17 +486,35 @@
                                 DatabaseUtils.QUERY_KEY_TIMESTAMP, queryTimestamp)
                         .build();
 
-        final Cursor cursor =
-                mProvider.query(
-                        batteryStateQueryContentUri,
-                        /*strings=*/ null,
-                        /*s=*/ null,
-                        /*strings1=*/ null,
-                        /*s1=*/ null);
+        final Cursor cursor = query(batteryStateQueryContentUri);
 
         return cursor;
     }
 
+    private Cursor getCursorOfLastFullChargeTimestamp() {
+        final Uri lastFullChargeTimestampContentUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.LAST_FULL_CHARGE_TIMESTAMP_PATH)
+                        .build();
+
+        return query(lastFullChargeTimestampContentUri);
+    }
+
+    private Cursor getCursorOfBatteryStateLatestTimestamp(final long queryTimestamp) {
+        final Uri batteryStateLatestTimestampUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.BATTERY_STATE_LATEST_TIMESTAMP_PATH)
+                        .appendQueryParameter(
+                                DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .build();
+
+        return query(batteryStateLatestTimestampUri);
+    }
+
     private void insertAppUsageEvent() {
         mProvider.onCreate();
         // Inserts some valid testing data.
@@ -452,12 +536,7 @@
                                 DatabaseUtils.QUERY_KEY_USERID, Long.toString(userId))
                         .build();
 
-        return mProvider.query(
-                appUsageLatestTimestampQueryContentUri,
-                /*strings=*/ null,
-                /*s=*/ null,
-                /*strings1=*/ null,
-                /*s1=*/ null);
+        return query(appUsageLatestTimestampQueryContentUri);
     }
 
     private Cursor getCursorOfAppUsage(final List<Long> userIds, final long queryTimestamp) {
@@ -474,7 +553,43 @@
                         .appendQueryParameter(DatabaseUtils.QUERY_KEY_USERID, queryUserIdString)
                         .build();
 
+        return query(appUsageEventUri);
+    }
+
+    private Cursor getCursorOfBatteryEvents(
+            final long queryTimestamp, final List<Integer> batteryEventTypes) {
+        final String batteryEventTypesString = batteryEventTypes.stream()
+                .map(type -> String.valueOf(type))
+                .collect(Collectors.joining(","));
+        final Uri batteryEventUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.BATTERY_EVENT_TABLE)
+                        .appendQueryParameter(
+                                DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .appendQueryParameter(
+                                DatabaseUtils.QUERY_BATTERY_EVENT_TYPE, batteryEventTypesString)
+                        .build();
+
+        return query(batteryEventUri);
+    }
+
+    private Cursor getCursorOfBatteryUsageSlots(final long queryTimestamp) {
+        final Uri batteryUsageSlotUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.BATTERY_USAGE_SLOT_TABLE)
+                        .appendQueryParameter(
+                                DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .build();
+
+        return query(batteryUsageSlotUri);
+    }
+
+    private Cursor query(Uri uri) {
         return mProvider.query(
-                appUsageEventUri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, /*s1=*/ null);
+                uri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, /*s1=*/ null);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
index 9aeff79..f3965fd 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -31,6 +32,7 @@
 import android.os.BatteryStatsManager;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.UserManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +45,7 @@
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
@@ -56,6 +59,8 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
+    private UserManager mUserManager;
+    @Mock
     private BatteryUsageStats mBatteryUsageStats;
     @Mock
     private BatteryEntry mMockBatteryEntry;
@@ -70,6 +75,7 @@
         doReturn(mBatteryStatsManager).when(mContext).getSystemService(
                 Context.BATTERY_STATS_SERVICE);
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
         doReturn(mMockContentResolver).when(mContext).getContentResolver();
         doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
     }
@@ -82,7 +88,7 @@
                 .thenReturn(mBatteryUsageStats);
         BatteryUsageDataLoader.sFakeBatteryEntryListSupplier = () -> batteryEntryList;
 
-        BatteryUsageDataLoader.loadUsageData(mContext, /*isFullChargeStart=*/ false);
+        BatteryUsageDataLoader.loadBatteryStatsData(mContext, /*isFullChargeStart=*/ false);
 
         final int queryFlags = mStatsQueryCaptor.getValue().getFlags();
         assertThat(queryFlags
@@ -97,7 +103,7 @@
                 .thenReturn(mBatteryUsageStats);
         BatteryUsageDataLoader.sFakeBatteryEntryListSupplier = () -> null;
 
-        BatteryUsageDataLoader.loadUsageData(mContext, /*isFullChargeStart=*/ false);
+        BatteryUsageDataLoader.loadBatteryStatsData(mContext, /*isFullChargeStart=*/ false);
 
         verify(mMockContentResolver).insert(any(), any());
     }
@@ -108,8 +114,51 @@
                 .thenReturn(mBatteryUsageStats);
         BatteryUsageDataLoader.sFakeBatteryEntryListSupplier = () -> new ArrayList<>();
 
-        BatteryUsageDataLoader.loadUsageData(mContext, /*isFullChargeStart=*/ false);
+        BatteryUsageDataLoader.loadBatteryStatsData(mContext, /*isFullChargeStart=*/ false);
 
         verify(mMockContentResolver).insert(any(), any());
     }
+
+    @Test
+    public void loadAppUsageData_withData_insertFakeDataIntoProvider() {
+        final List<AppUsageEvent> AppUsageEventList = new ArrayList<>();
+        final AppUsageEvent appUsageEvent = AppUsageEvent.newBuilder().setUid(0).build();
+        AppUsageEventList.add(appUsageEvent);
+        BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
+        BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> AppUsageEventList;
+
+        BatteryUsageDataLoader.loadAppUsageData(mContext);
+
+        verify(mMockContentResolver).bulkInsert(any(), any());
+        verify(mMockContentResolver).notifyChange(any(), any());
+    }
+
+    @Test
+    public void loadAppUsageData_nullAppUsageEvents_notInsertDataIntoProvider() {
+        BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> null;
+
+        BatteryUsageDataLoader.loadAppUsageData(mContext);
+
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
+
+    @Test
+    public void loadAppUsageData_nullUsageEventsList_notInsertDataIntoProvider() {
+        BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
+        BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> null;
+
+        BatteryUsageDataLoader.loadAppUsageData(mContext);
+
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
+
+    @Test
+    public void loadAppUsageData_emptyUsageEventsList_notInsertDataIntoProvider() {
+        BatteryUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
+        BatteryUsageDataLoader.sFakeUsageEventsListSupplier = () -> new ArrayList<>();
+
+        BatteryUsageDataLoader.loadAppUsageData(mContext);
+
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
index aa1ebd7..566df52 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -22,8 +22,10 @@
 
 import android.app.AlarmManager;
 import android.app.Application;
+import android.app.usage.UsageStatsManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -40,7 +42,6 @@
 import org.robolectric.shadows.ShadowAlarmManager;
 
 import java.time.Clock;
-import java.time.Duration;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -65,10 +66,12 @@
         BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, Clock.systemUTC().millis(), "com.android.systemui");
         mDao = database.batteryStateDao();
+        clearSharedPreferences();
     }
 
     @After
     public void tearDown() {
+        clearSharedPreferences();
         mPeriodicJobManager.reset();
     }
 
@@ -82,8 +85,21 @@
 
     @Test
     public void onReceive_withBootCompletedIntent_refreshesJob() {
+        final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext);
+        sharedPreferences
+                .edit()
+                .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE,
+                        UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY)
+                .apply();
+
         mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED));
+
         assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
+        assertThat(
+                DatabaseUtils
+                        .getSharedPreferences(mContext)
+                        .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE))
+                .isFalse();
     }
 
     @Test
@@ -133,15 +149,7 @@
                 BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK);
     }
 
-    private void insertExpiredData(int shiftDay) {
-        final long expiredTimeInMs =
-                Clock.systemUTC().millis() - Duration.ofDays(shiftDay).toMillis();
-        BatteryTestUtils.insertDataToBatteryStateTable(
-                mContext, expiredTimeInMs - 1, "com.android.systemui");
-        BatteryTestUtils.insertDataToBatteryStateTable(
-                mContext, expiredTimeInMs, "com.android.systemui");
-        // Ensures the testing environment is correct.
-        assertThat(mDao.getAllAfter(0)).hasSize(3);
+    private void clearSharedPreferences() {
+        DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply();
     }
-
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index 6b8073b..f06dc63 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -35,11 +35,11 @@
 import android.os.BatteryManager;
 import android.os.BatteryUsageStats;
 import android.os.LocaleList;
-import android.os.RemoteException;
 import android.os.UserHandle;
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,7 +49,10 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 import java.util.TimeZone;
 
 @RunWith(RobolectricTestRunner.class)
@@ -62,15 +65,17 @@
     @Mock
     private BatteryUsageStats mBatteryUsageStats;
     @Mock
-    private IUsageStatsManager mUsageStatsManager;
-    @Mock
     private BatteryEntry mMockBatteryEntry;
+    @Mock
+    private IUsageStatsManager mUsageStatsManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
+        ConvertUtils.sUsageSource = ConvertUtils.EMPTY_USAGE_SOURCE;
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
+        DataProcessor.sUsageStatsManager = mUsageStatsManager;
     }
 
     @Test
@@ -212,6 +217,22 @@
     }
 
     @Test
+    public void convertBatteryUsageSlotToContentValues_normalCase_returnsExpectedContentValues() {
+        final BatteryUsageSlot batteryUsageSlot =
+                BatteryUsageSlot.newBuilder()
+                        .setStartTimestamp(10001L)
+                        .setEndTimestamp(30003L)
+                        .setStartBatteryLevel(88)
+                        .setEndBatteryLevel(66)
+                        .setScreenOnTime(123L)
+                        .build();
+        final ContentValues values =
+                ConvertUtils.convertBatteryUsageSlotToContentValues(batteryUsageSlot);
+        assertThat(values.getAsLong(BatteryUsageSlotEntity.KEY_TIMESTAMP)).isEqualTo(10001L);
+        assertThat(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT).isNotEmpty();
+    }
+
+    @Test
     public void convertToBatteryHistEntry_returnsExpectedResult() {
         final int expectedType = 3;
         when(mMockBatteryEntry.getUid()).thenReturn(1001);
@@ -322,8 +343,8 @@
         when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1001);
 
         final long userId = 1;
-        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
-                mContext, mUsageStatsManager, event, userId);
+        final AppUsageEvent appUsageEvent =
+                ConvertUtils.convertToAppUsageEvent(mContext, mUsageStatsManager, event, userId);
         assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L);
         assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN);
         assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
@@ -338,8 +359,9 @@
         final Event event = new Event();
         event.mPackage = null;
 
-        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
-                mContext, mUsageStatsManager, event, /*userId=*/ 0);
+        final AppUsageEvent appUsageEvent =
+                ConvertUtils.convertToAppUsageEvent(
+                        mContext, mUsageStatsManager, event, /*userId=*/ 0);
 
         assertThat(appUsageEvent).isNull();
     }
@@ -354,14 +376,14 @@
                 .thenThrow(new PackageManager.NameNotFoundException());
 
         final long userId = 1;
-        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
-                mContext, mUsageStatsManager, event, userId);
+        final AppUsageEvent appUsageEvent =
+                ConvertUtils.convertToAppUsageEvent(mContext, mUsageStatsManager, event, userId);
 
         assertThat(appUsageEvent).isNull();
     }
 
     @Test
-    public void convertToAppUsageEventFromCursor_returnExpectedResult() {
+    public void convertToAppUsageEvent_returnExpectedResult() {
         final MatrixCursor cursor = new MatrixCursor(
                 new String[]{
                         AppUsageEventEntity.KEY_UID,
@@ -382,7 +404,7 @@
                         100001L});
         cursor.moveToFirst();
 
-        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEventFromCursor(cursor);
+        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(cursor);
 
         assertThat(appUsageEvent.getUid()).isEqualTo(101L);
         assertThat(appUsageEvent.getUserId()).isEqualTo(1001L);
@@ -394,7 +416,7 @@
     }
 
     @Test
-    public void convertToAppUsageEventFromCursor_emptyInstanceIdAndRootName_returnExpectedResult() {
+    public void convertToAppUsageEvent_emptyInstanceIdAndRootName_returnExpectedResult() {
         final MatrixCursor cursor = new MatrixCursor(
                 new String[]{
                         AppUsageEventEntity.KEY_UID,
@@ -411,7 +433,7 @@
                         AppUsageEventType.DEVICE_SHUTDOWN.getNumber()});
         cursor.moveToFirst();
 
-        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEventFromCursor(cursor);
+        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(cursor);
 
         assertThat(appUsageEvent.getUid()).isEqualTo(101L);
         assertThat(appUsageEvent.getUserId()).isEqualTo(1001L);
@@ -432,6 +454,42 @@
     }
 
     @Test
+    public void convertToBatteryEventList_normalCase_returnsExpectedResult() {
+        final BatteryLevelData batteryLevelData = new BatteryLevelData(Map.of(
+                1691589600000L, 98, 1691596800000L, 90, 1691596812345L, 80));
+
+        final List<BatteryEvent> batteryEventList =
+                ConvertUtils.convertToBatteryEventList(batteryLevelData);
+
+        assertThat(batteryEventList).hasSize(2);
+        assertThat(batteryEventList.get(0).getTimestamp()).isEqualTo(1691589600000L);
+        assertThat(batteryEventList.get(0).getType()).isEqualTo(BatteryEventType.EVEN_HOUR);
+        assertThat(batteryEventList.get(0).getBatteryLevel()).isEqualTo(98);
+        assertThat(batteryEventList.get(1).getTimestamp()).isEqualTo(1691596800000L);
+        assertThat(batteryEventList.get(1).getType()).isEqualTo(BatteryEventType.EVEN_HOUR);
+        assertThat(batteryEventList.get(1).getBatteryLevel()).isEqualTo(90);
+    }
+
+    @Test
+    public void convertToBatteryUsageSlotList_normalCase_returnsExpectedResult() {
+        BatteryDiffData batteryDiffData1 = new BatteryDiffData(
+                mContext, 11L, 12L, 13, 14, 15, List.of(), List.of(), Set.of(), Set.of(), false);
+        BatteryDiffData batteryDiffData2 = new BatteryDiffData(
+                mContext, 21L, 22L, 23, 24, 25, List.of(), List.of(), Set.of(), Set.of(), false);
+        BatteryDiffData batteryDiffData3 = new BatteryDiffData(
+                mContext, 31L, 32L, 33, 34, 35, List.of(), List.of(), Set.of(), Set.of(), false);
+        final Map<Long, BatteryDiffData> batteryDiffDataMap = Map.of(
+                11L, batteryDiffData1, 21L, batteryDiffData2, 31L, batteryDiffData3);
+
+        final List<BatteryUsageSlot> batteryUsageSlotList =
+                ConvertUtils.convertToBatteryUsageSlotList(batteryDiffDataMap);
+
+        assertThat(batteryUsageSlotList).hasSize(3);
+        assertThat(batteryUsageSlotList.stream().map((s) -> s.getScreenOnTime()).sorted().toList())
+                .isEqualTo(List.of(15L, 25L, 35L));
+    }
+
+    @Test
     public void getLocale_nullContext_returnDefaultLocale() {
         assertThat(ConvertUtils.getLocale(/*context=*/ null))
                 .isEqualTo(Locale.getDefault());
@@ -450,51 +508,47 @@
     }
 
     @Test
-    public void getEffectivePackageName_currentActivity_returnPackageName() throws RemoteException {
-        when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_CURRENT_ACTIVITY);
+    public void getEffectivePackageName_currentActivity_returnPackageName() {
+        ConvertUtils.sUsageSource = USAGE_SOURCE_CURRENT_ACTIVITY;
         final String packageName = "com.android.settings1";
         final String taskRootPackageName = "com.android.settings2";
 
         assertThat(ConvertUtils.getEffectivePackageName(
-                mUsageStatsManager, packageName, taskRootPackageName))
+                mContext, mUsageStatsManager, packageName, taskRootPackageName))
                 .isEqualTo(packageName);
     }
 
     @Test
-    public void getEffectivePackageName_usageSourceThrowException_returnPackageName()
-            throws RemoteException {
-        when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException());
+    public void getEffectivePackageName_emptyUsageSource_returnPackageName() {
         final String packageName = "com.android.settings1";
         final String taskRootPackageName = "com.android.settings2";
 
         assertThat(ConvertUtils.getEffectivePackageName(
-                mUsageStatsManager, packageName, taskRootPackageName))
+                mContext, mUsageStatsManager, packageName, taskRootPackageName))
                 .isEqualTo(packageName);
     }
 
     @Test
-    public void getEffectivePackageName_rootActivity_returnTaskRootPackageName()
-            throws RemoteException {
-        when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+    public void getEffectivePackageName_rootActivity_returnTaskRootPackageName() {
+        ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY;
         final String packageName = "com.android.settings1";
         final String taskRootPackageName = "com.android.settings2";
 
         assertThat(ConvertUtils.getEffectivePackageName(
-                mUsageStatsManager, packageName, taskRootPackageName))
+                mContext, mUsageStatsManager, packageName, taskRootPackageName))
                 .isEqualTo(taskRootPackageName);
     }
 
     @Test
-    public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName()
-            throws RemoteException {
-        when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+    public void getEffectivePackageName_nullOrEmptyTaskRoot_returnPackageName() {
+        ConvertUtils.sUsageSource = USAGE_SOURCE_TASK_ROOT_ACTIVITY;
         final String packageName = "com.android.settings1";
 
         assertThat(ConvertUtils.getEffectivePackageName(
-                mUsageStatsManager, packageName, /*taskRootPackageName=*/ null))
+                mContext, mUsageStatsManager, packageName, /*taskRootPackageName=*/ null))
                 .isEqualTo(packageName);
         assertThat(ConvertUtils.getEffectivePackageName(
-                mUsageStatsManager, packageName, /*taskRootPackageName=*/ ""))
+                mContext, mUsageStatsManager, packageName, /*taskRootPackageName=*/ ""))
                 .isEqualTo(packageName);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
index b610cfb..94fa00f 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
@@ -30,8 +30,12 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.os.BatteryManager;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserManager;
@@ -39,9 +43,12 @@
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
@@ -52,6 +59,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 @RunWith(RobolectricTestRunner.class)
 public final class DataProcessManagerTest {
@@ -65,7 +73,13 @@
     @Mock
     private UserManager mUserManager;
     @Mock
+    private BatteryStatsManager mBatteryStatsManager;
+    @Mock
+    private BatteryUsageStats mBatteryUsageStats;
+    @Mock
     private Intent mIntent;
+    @Captor
+    private ArgumentCaptor<BatteryUsageStatsQuery> mBatteryUsageStatsQueryCaptor;
 
     @Before
     public void setUp() {
@@ -77,22 +91,32 @@
         doReturn(mUserManager)
                 .when(mContext)
                 .getSystemService(UserManager.class);
+        doReturn(mBatteryStatsManager).when(mContext).getSystemService(
+                Context.BATTERY_STATS_SERVICE);
+        doReturn(mBatteryUsageStats).when(
+                mBatteryStatsManager).getBatteryUsageStats(mBatteryUsageStatsQueryCaptor.capture());
         doReturn(mIntent).when(mContext).registerReceiver(any(), any());
         doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt());
         doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt());
 
         mDataProcessManager = new DataProcessManager(
                 mContext, /*handler=*/ null,  /*rawStartTimestamp=*/ 0L,
-                /*callbackFunction=*/ null, /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(),
+                /*lastFullChargeTimestamp=*/ 0L, /*callbackFunction=*/ null,
+                /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(),
                 /*batteryHistoryMap=*/ new HashMap<>());
     }
 
+    @After
+    public void cleanUp() {
+        DatabaseUtils.sFakeSupplier = null;
+        DataProcessManager.sFakeBatteryHistoryMap = null;
+    }
+
     @Test
     public void constructor_noLevelData() {
         final DataProcessManager dataProcessManager =
                 new DataProcessManager(mContext, /*handler=*/ null, /*callbackFunction=*/ null);
         assertThat(dataProcessManager.getShowScreenOnTime()).isFalse();
-        assertThat(dataProcessManager.getShowBatteryLevel()).isFalse();
     }
 
     @Test
@@ -122,16 +146,18 @@
         final String packageName = "package";
         // Adds the day 1 data.
         final List<Long> timestamps1 = List.of(2L, 3L, 4L);
-        final List<Integer> levels1 = List.of(100, 100, 100);
+        final Map<Long, Integer> batteryLevelMap1 =
+                Map.of(timestamps1.get(0), 100, timestamps1.get(1), 100, timestamps1.get(2), 100);
         hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps1, levels1));
+                new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap1, timestamps1));
         // Adds the day 2 data.
         hourlyBatteryLevelsPerDay.add(null);
         // Adds the day 3 data.
         final List<Long> timestamps2 = List.of(5L, 6L);
-        final List<Integer> levels2 = List.of(100, 100);
+        final Map<Long, Integer> batteryLevelMap2 =
+                Map.of(timestamps2.get(0), 100, timestamps2.get(1), 100);
         hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps2, levels2));
+                new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap2, timestamps2));
         // Fake current usage data.
         final UsageEvents.Event event1 =
                 getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1, packageName);
@@ -171,10 +197,18 @@
         cursor.addRow(new Object[] {
                 AppUsageEventType.ACTIVITY_STOPPED.getNumber(), /*timestamp=*/ 6, /*userId=*/ 1,
                 /*instanceId=*/ 2, packageName});
-        DatabaseUtils.sFakeSupplier = () -> cursor;
+        DatabaseUtils.sFakeSupplier = new Supplier<>() {
+            private int mTimes = 0;
+            @Override
+            public Cursor get() {
+                mTimes++;
+                return mTimes <= 2 ? null : cursor;
+            }
+        };
 
         final DataProcessManager dataProcessManager = new DataProcessManager(
-                mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 2L,  /*callbackFunction=*/ null,
+                mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 2L,
+                /*lastFullChargeTimestamp=*/ 1L, /*callbackFunction=*/ null,
                 hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ new HashMap<>());
         dataProcessManager.start();
 
@@ -254,12 +288,13 @@
         assertThat(DataProcessManager.getBatteryLevelData(
                 mContext,
                 /*handler=*/ null,
-                /*batteryHistoryMap=*/ null,
-                /*asyncResponseDelegate=*/ null))
-                .isNull();
+                /*isFromPeriodJob=*/ false,
+                /*asyncResponseDelegate=*/ null)).isNull();
         assertThat(DataProcessManager.getBatteryLevelData(
-                mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null))
-                .isNull();
+                mContext,
+                /*handler=*/ null,
+                /*isFromPeriodJob=*/ true,
+                /*asyncResponseDelegate=*/ null)).isNull();
     }
 
     @Test
@@ -270,18 +305,16 @@
                 DateUtils.HOUR_IN_MILLIS * 2 - 200L,
                 DateUtils.HOUR_IN_MILLIS * 2 - 100L};
         final int[] levels = {100, 99, 98};
-        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
-                createHistoryMap(timestamps, levels);
+        DataProcessManager.sFakeBatteryHistoryMap = createHistoryMap(timestamps, levels);
         DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1];
 
         final BatteryLevelData resultData =
                 DataProcessManager.getBatteryLevelData(
                         mContext,
                         /*handler=*/ null,
-                        batteryHistoryMap,
+                        /*isFromPeriodJob=*/ false,
                         /*asyncResponseDelegate=*/ null);
 
-
         final List<Long> expectedDailyTimestamps = List.of(
                 DateUtils.HOUR_IN_MILLIS * 2 - 300L,
                 DateUtils.HOUR_IN_MILLIS * 2 - 100L);
@@ -301,15 +334,14 @@
         // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00
         final long[] timestamps = {1640966400000L, 1640970000000L};
         final int[] levels = {100, 99};
-        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
-                createHistoryMap(timestamps, levels);
+        DataProcessManager.sFakeBatteryHistoryMap = createHistoryMap(timestamps, levels);
         DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1];
 
         final BatteryLevelData resultData =
                 DataProcessManager.getBatteryLevelData(
                         mContext,
                         /*handler=*/ null,
-                        batteryHistoryMap,
+                        /*isFromPeriodJob=*/ false,
                         /*asyncResponseDelegate=*/ null);
 
         final List<Long> expectedDailyTimestamps = List.of(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index e2274e2..c4394f7 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.FAKE_PACKAGE_NAME;
+import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.anyInt;
@@ -42,6 +45,7 @@
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.util.ArrayMap;
 
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -188,16 +192,18 @@
         final String packageName = "com.android.settings";
         // Adds the day 1 data.
         final List<Long> timestamps1 = List.of(14400000L, 18000000L, 21600000L);
-        final List<Integer> levels1 = List.of(100, 100, 100);
+        final Map<Long, Integer> batteryLevelMap1 =
+                Map.of(timestamps1.get(0), 100, timestamps1.get(1), 100, timestamps1.get(2), 100);
         hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps1, levels1));
+                new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap1, timestamps1));
         // Adds the day 2 data.
         hourlyBatteryLevelsPerDay.add(null);
         // Adds the day 3 data.
         final List<Long> timestamps2 = List.of(45200000L, 48800000L);
-        final List<Integer> levels2 = List.of(100, 100);
+        final Map<Long, Integer> batteryLevelMap2 =
+                Map.of(timestamps2.get(0), 100, timestamps2.get(1), 100);
         hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps2, levels2));
+                new BatteryLevelData.PeriodBatteryLevelData(batteryLevelMap2, timestamps2));
         final List<AppUsageEvent> appUsageEventList = new ArrayList<>();
         // Adds some events before the start timestamp.
         appUsageEventList.add(buildAppUsageEvent(
@@ -249,7 +255,7 @@
 
         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> periodMap =
                 DataProcessor.generateAppUsagePeriodMap(
-                        14400000L, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>());
+                        mContext, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>());
 
         assertThat(periodMap).hasSize(3);
         // Day 1
@@ -285,9 +291,10 @@
         final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
                 new ArrayList<>();
         hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
+                new BatteryLevelData.PeriodBatteryLevelData(new ArrayMap<>(), new ArrayList<>()));
         assertThat(DataProcessor.generateAppUsagePeriodMap(
-                0L, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())).isNull();
+                mContext, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>()))
+                .isNull();
     }
 
     @Test
@@ -370,19 +377,6 @@
     }
 
     @Test
-    public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() {
-        final long[] timestamps = {100L};
-        final int[] levels = {100};
-        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
-                createHistoryMap(timestamps, levels);
-        DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1];
-
-        assertThat(
-                DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap))
-                .isNull();
-    }
-
-    @Test
     public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() {
         // Timezone GMT+8
         final long[] timestamps = {
@@ -440,7 +434,7 @@
         );
         final List<Integer> expectedDailyLevels = new ArrayList<>();
         expectedDailyLevels.add(100);
-        expectedDailyLevels.add(null);
+        expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN);
         expectedDailyLevels.add(82);
         final List<List<Long>> expectedHourlyTimestamps = List.of(
                 List.of(
@@ -458,13 +452,13 @@
         );
         final List<Integer> expectedHourlyLevels1 = new ArrayList<>();
         expectedHourlyLevels1.add(100);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
         final List<Integer> expectedHourlyLevels2 = new ArrayList<>();
-        expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
         expectedHourlyLevels2.add(94);
         expectedHourlyLevels2.add(90);
-        expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
         expectedHourlyLevels2.add(82);
         final List<List<Integer>> expectedHourlyLevels = List.of(
                 expectedHourlyLevels1,
@@ -502,8 +496,8 @@
         );
         final List<Integer> expectedDailyLevels = new ArrayList<>();
         expectedDailyLevels.add(100);
-        expectedDailyLevels.add(null);
-        expectedDailyLevels.add(null);
+        expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN);
+        expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN);
         expectedDailyLevels.add(88);
         final List<List<Long>> expectedHourlyTimestamps = List.of(
                 List.of(
@@ -541,32 +535,32 @@
         );
         final List<Integer> expectedHourlyLevels1 = new ArrayList<>();
         expectedHourlyLevels1.add(100);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
-        expectedHourlyLevels1.add(null);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
         final List<Integer> expectedHourlyLevels2 = new ArrayList<>();
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
         final List<Integer> expectedHourlyLevels3 = new ArrayList<>();
-        expectedHourlyLevels3.add(null);
-        expectedHourlyLevels3.add(null);
-        expectedHourlyLevels3.add(null);
+        expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN);
         expectedHourlyLevels3.add(88);
         final List<List<Integer>> expectedHourlyLevels = List.of(
                 expectedHourlyLevels1,
@@ -605,8 +599,8 @@
         );
         final List<Integer> expectedDailyLevels = new ArrayList<>();
         expectedDailyLevels.add(100);
-        expectedDailyLevels.add(null);
-        expectedDailyLevels.add(null);
+        expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN);
+        expectedDailyLevels.add(BATTERY_LEVEL_UNKNOWN);
         expectedDailyLevels.add(88);
         final List<List<Long>> expectedHourlyTimestamps = List.of(
                 List.of(
@@ -637,25 +631,25 @@
         );
         final List<Integer> expectedHourlyLevels1 = new ArrayList<>();
         expectedHourlyLevels1.add(100);
-        expectedHourlyLevels1.add(null);
+        expectedHourlyLevels1.add(BATTERY_LEVEL_UNKNOWN);
         final List<Integer> expectedHourlyLevels2 = new ArrayList<>();
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
-        expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels2.add(BATTERY_LEVEL_UNKNOWN);
         final List<Integer> expectedHourlyLevels3 = new ArrayList<>();
-        expectedHourlyLevels3.add(null);
-        expectedHourlyLevels3.add(null);
-        expectedHourlyLevels3.add(null);
+        expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN);
+        expectedHourlyLevels3.add(BATTERY_LEVEL_UNKNOWN);
         expectedHourlyLevels3.add(88);
         final List<List<Integer>> expectedHourlyLevels = List.of(
                 expectedHourlyLevels1,
@@ -734,141 +728,6 @@
     }
 
     @Test
-    public void getDailyTimestamps_notEnoughData_returnEmptyList() {
-        assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty();
-        assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty();
-    }
-
-    @Test
-    public void getDailyTimestamps_allDataInOneHour_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1640970006000L, // 2022-01-01 01:00:06
-                1640973608000L  // 2022-01-01 01:00:08
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1640970006000L, // 2022-01-01 01:00:06
-                1640973608000L  // 2022-01-01 01:00:08
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
-    public void getDailyTimestamps_OneHourDataPerDay_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1641049200000L, // 2022-01-01 23:00:00
-                1641052800000L, // 2022-01-02 00:00:00
-                1641056400000L  // 2022-01-02 01:00:00
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1641049200000L, // 2022-01-01 23:00:00
-                1641052800000L, // 2022-01-02 00:00:00
-                1641056400000L  // 2022-01-02 01:00:00
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
-    public void getDailyTimestamps_OneDayData_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1640966400000L, // 2022-01-01 00:00:00
-                1640970000000L, // 2022-01-01 01:00:00
-                1640973600000L, // 2022-01-01 02:00:00
-                1640977200000L, // 2022-01-01 03:00:00
-                1640980800000L  // 2022-01-01 04:00:00
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1640966400000L, // 2022-01-01 00:00:00
-                1640980800000L  // 2022-01-01 04:00:00
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
-    public void getDailyTimestamps_MultipleDaysData_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1641045600000L, // 2022-01-01 22:00:00
-                1641060000000L, // 2022-01-02 02:00:00
-                1641160800000L, // 2022-01-03 06:00:00
-                1641232800000L  // 2022-01-04 02:00:00
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1641045600000L, // 2022-01-01 22:00:00
-                1641052800000L, // 2022-01-02 00:00:00
-                1641139200000L, // 2022-01-03 00:00:00
-                1641225600000L, // 2022-01-04 00:00:00
-                1641232800000L  // 2022-01-04 02:00:00
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
-    public void getDailyTimestamps_FirstDayOneHourData_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1641049200000L, // 2022-01-01 23:00:00
-                1641060000000L, // 2022-01-02 02:00:00
-                1641160800000L, // 2022-01-03 06:00:00
-                1641254400000L  // 2022-01-04 08:00:00
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1641049200000L, // 2022-01-01 23:00:00
-                1641052800000L, // 2022-01-02 00:00:00
-                1641139200000L, // 2022-01-03 00:00:00
-                1641225600000L, // 2022-01-04 00:00:00
-                1641254400000L  // 2022-01-04 08:00:00
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
-    public void getDailyTimestamps_LastDayNoData_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1640988000000L, // 2022-01-01 06:00:00
-                1641060000000L, // 2022-01-02 02:00:00
-                1641160800000L, // 2022-01-03 06:00:00
-                1641225600000L  // 2022-01-04 00:00:00
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1640988000000L, // 2022-01-01 06:00:00
-                1641052800000L, // 2022-01-02 00:00:00
-                1641139200000L, // 2022-01-03 00:00:00
-                1641225600000L  // 2022-01-04 00:00:00
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
-    public void getDailyTimestamps_LastDayOneHourData_returnExpectedList() {
-        // Timezone GMT+8
-        final List<Long> timestamps = List.of(
-                1640988000000L, // 2022-01-01 06:00:00
-                1641060000000L, // 2022-01-02 02:00:00
-                1641160800000L, // 2022-01-03 06:00:00
-                1641229200000L  // 2022-01-04 01:00:00
-        );
-
-        final List<Long> expectedTimestamps = List.of(
-                1640988000000L, // 2022-01-01 06:00:00
-                1641052800000L, // 2022-01-02 00:00:00
-                1641139200000L, // 2022-01-03 00:00:00
-                1641225600000L, // 2022-01-04 00:00:00
-                1641229200000L  // 2022-01-04 01:00:00
-        );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
-    }
-
-    @Test
     public void isFromFullCharge_emptyData_returnFalse() {
         assertThat(DataProcessor.isFromFullCharge(null)).isFalse();
         assertThat(DataProcessor.isFromFullCharge(new HashMap<>())).isFalse();
@@ -915,20 +774,53 @@
     }
 
     @Test
-    public void getBatteryUsageMap_emptyHistoryMap_returnNull() {
+    public void getBatteryDiffDataMap_emptyHistoryMap_returnEmpty() {
         final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
                 new ArrayList<>();
 
         hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
+                new BatteryLevelData.PeriodBatteryLevelData(new ArrayMap<>(), new ArrayList<>()));
 
-        assertThat(DataProcessor.getBatteryUsageMap(
-                mContext, hourlyBatteryLevelsPerDay, new HashMap<>(), /*appUsagePeriodMap=*/ null))
-                .isNull();
+        assertThat(DataProcessor.getBatteryDiffDataMap(mContext, hourlyBatteryLevelsPerDay,
+                new HashMap<>(), /*appUsagePeriodMap=*/ null, Set.of(), Set.of())).isEmpty();
     }
 
     @Test
-    public void getBatteryUsageMap_returnsExpectedResult() {
+    public void getBatteryDiffDataMap_normalFlow_returnExpectedResult() {
+        final int userId = mContext.getUserId();
+        final long[] batteryHistoryKeys = new long[]{
+                1641045600000L, // 2022-01-01 22:00:00
+                1641049200000L, // 2022-01-01 23:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+        };
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = Map.of(
+                batteryHistoryKeys[0], Map.of(FAKE_PACKAGE_NAME, createBatteryHistEntry(
+                        FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, 0, 0,
+                        0, 0, 0L, userId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, 0L, 0L, false)),
+                batteryHistoryKeys[1], Map.of(FAKE_PACKAGE_NAME, createBatteryHistEntry(
+                        FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 5, 0, 0,
+                        0, 0, 0L, userId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, 0L, 0L, false)),
+                batteryHistoryKeys[2], Map.of(FAKE_PACKAGE_NAME, createBatteryHistEntry(
+                        FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 16, 0, 0,
+                        0, 0, 0L, userId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, 0L, 0L, false)));
+        final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys);
+        final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
+                appUsagePeriodMap = Map.of(0, Map.of(0, Map.of(Long.valueOf(userId), Map.of(
+                FAKE_PACKAGE_NAME, List.of(buildAppUsagePeriod(0, 6))))));
+
+        Map<Long, BatteryDiffData> batteryDiffDataMap = DataProcessor.getBatteryDiffDataMap(
+                mContext, batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap,
+                appUsagePeriodMap, Set.of(), Set.of());
+
+        assertThat(batteryDiffDataMap).hasSize(1);
+        assertThat(batteryDiffDataMap).containsKey(batteryHistoryKeys[0]);
+        BatteryDiffData batteryDiffData = batteryDiffDataMap.get(batteryHistoryKeys[0]);
+        assertThat(batteryDiffData.getStartTimestamp()).isEqualTo(batteryHistoryKeys[0]);
+        assertThat(batteryDiffData.getEndTimestamp()).isEqualTo(batteryHistoryKeys[2]);
+    }
+
+    @Test
+    public void generateBatteryUsageMap_returnsExpectedResult() {
         final long[] batteryHistoryKeys = new long[]{
                 1641045600000L, // 2022-01-01 22:00:00
                 1641049200000L, // 2022-01-01 23:00:00
@@ -939,7 +831,7 @@
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
         final int currentUserId = mContext.getUserId();
         final BatteryHistEntry fakeEntry = createBatteryHistEntry(
-                ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0,
+                FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0,
                 /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0,
                 /*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0,
                 /*uid=*/ 0L, currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
@@ -1029,19 +921,7 @@
         entryMap.put(entry.getKey(), entry);
         entryMap.put(fakeEntry.getKey(), fakeEntry);
         batteryHistoryMap.put(batteryHistoryKeys[4], entryMap);
-        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
-                new ArrayList<>();
-        // Adds the day 1 data.
-        List<Long> timestamps =
-                List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
-        final List<Integer> levels = List.of(100, 100);
-        hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
-        // Adds the day 2 data.
-        timestamps = List.of(batteryHistoryKeys[2], batteryHistoryKeys[4]);
-        hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
-
+        final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys);
         // Adds app usage data to test screen on time.
         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
                 appUsagePeriodMap = new HashMap<>();
@@ -1065,8 +945,12 @@
         appUsagePeriodMap.get(1).put(0, appUsageMap);
 
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
-                DataProcessor.getBatteryUsageMap(
-                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap);
+                DataProcessor.generateBatteryUsageMap(
+                        mContext,
+                        DataProcessor.getBatteryDiffDataMap(mContext,
+                                batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap,
+                                appUsagePeriodMap, Set.of(), Set.of()),
+                        batteryLevelData);
 
         BatteryDiffData resultDiffData =
                 resultMap
@@ -1127,7 +1011,7 @@
     }
 
     @Test
-    public void getBatteryUsageMap_multipleUsers_returnsExpectedResult() {
+    public void generateBatteryUsageMap_multipleUsers_returnsExpectedResult() {
         final long[] batteryHistoryKeys = new long[]{
                 1641052800000L, // 2022-01-02 00:00:00
                 1641056400000L, // 2022-01-02 01:00:00
@@ -1216,17 +1100,15 @@
                 /*backgroundUsageTimeInMs=*/ 30L, /*isHidden=*/ false);
         entryMap.put(entry.getKey(), entry);
         batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
-        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
-                new ArrayList<>();
-        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
-        final List<Integer> levels = List.of(100, 100);
-        hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys);
 
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
-                DataProcessor.getBatteryUsageMap(
-                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap,
-                        /*appUsagePeriodMap=*/ null);
+                DataProcessor.generateBatteryUsageMap(
+                        mContext,
+                        DataProcessor.getBatteryDiffDataMap(mContext,
+                                batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap,
+                                /*appUsagePeriodMap=*/ null, Set.of(), Set.of()),
+                        batteryLevelData);
 
         final BatteryDiffData resultDiffData =
                 resultMap
@@ -1246,7 +1128,7 @@
     }
 
     @Test
-    public void getBatteryUsageMap_usageTimeExceed_returnsExpectedResult() {
+    public void generateBatteryUsageMap_usageTimeExceed_returnsExpectedResult() {
         final long[] batteryHistoryKeys = new long[]{
                 1641052800000L, // 2022-01-02 00:00:00
                 1641056400000L, // 2022-01-02 01:00:00
@@ -1287,12 +1169,7 @@
                 /*backgroundUsageTimeInMs=*/ 7200000L, /*isHidden=*/ false);
         entryMap.put(entry.getKey(), entry);
         batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
-        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
-                new ArrayList<>();
-        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
-        final List<Integer> levels = List.of(100, 100);
-        hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys);
 
         // Adds app usage data to test screen on time.
         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
@@ -1306,8 +1183,12 @@
         appUsagePeriodMap.get(0).put(0, appUsageMap);
 
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
-                DataProcessor.getBatteryUsageMap(
-                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap);
+                DataProcessor.generateBatteryUsageMap(
+                        mContext,
+                        DataProcessor.getBatteryDiffDataMap(mContext,
+                                batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap,
+                                appUsagePeriodMap, Set.of(), Set.of()),
+                        batteryLevelData);
 
         final BatteryDiffData resultDiffData =
                 resultMap
@@ -1337,7 +1218,7 @@
     }
 
     @Test
-    public void getBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() {
+    public void generateBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() {
         final long[] batteryHistoryKeys = new long[]{
                 1641052800000L, // 2022-01-02 00:00:00
                 1641056400000L, // 2022-01-02 01:00:00
@@ -1402,19 +1283,17 @@
                 /*backgroundUsageTimeInMs=*/ 20L, /*isHidden=*/ false);
         entryMap.put(entry.getKey(), entry);
         batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
-        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
-                new ArrayList<>();
-        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
-        final List<Integer> levels = List.of(100, 100);
-        hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys);
         when(mPowerUsageFeatureProvider.getHideApplicationSet())
                 .thenReturn(Set.of("package1"));
 
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
-                DataProcessor.getBatteryUsageMap(
-                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap,
-                        /*appUsagePeriodMap=*/ null);
+                DataProcessor.generateBatteryUsageMap(
+                        mContext,
+                        DataProcessor.getBatteryDiffDataMap(mContext,
+                                batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap,
+                                /*appUsagePeriodMap=*/ null, Set.of(), Set.of()),
+                        batteryLevelData);
 
         final BatteryDiffData resultDiffData =
                 resultMap
@@ -1430,7 +1309,7 @@
     }
 
     @Test
-    public void getBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() {
+    public void generateBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() {
         final long[] batteryHistoryKeys = new long[]{
                 1641052800000L, // 2022-01-02 00:00:00
                 1641056400000L, // 2022-01-02 01:00:00
@@ -1495,19 +1374,17 @@
                 /*backgroundUsageTimeInMs=*/ 20L, /*isHidden=*/ false);
         entryMap.put(entry.getKey(), entry);
         batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
-        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
-                new ArrayList<>();
-        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
-        final List<Integer> levels = List.of(100, 100);
-        hourlyBatteryLevelsPerDay.add(
-                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        final BatteryLevelData batteryLevelData = generateBatteryLevelData(batteryHistoryKeys);
         when(mPowerUsageFeatureProvider.getHideBackgroundUsageTimeSet())
                 .thenReturn(new HashSet(Arrays.asList((CharSequence) "package2")));
 
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
-                DataProcessor.getBatteryUsageMap(
-                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap,
-                        /*appUsagePeriodMap=*/ null);
+                DataProcessor.generateBatteryUsageMap(
+                        mContext,
+                        DataProcessor.getBatteryDiffDataMap(mContext,
+                                batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryHistoryMap,
+                                /*appUsagePeriodMap=*/ null, Set.of(), Set.of()),
+                        batteryLevelData);
 
         final BatteryDiffData resultDiffData =
                 resultMap
@@ -1522,7 +1399,10 @@
     @Test
     public void generateBatteryDiffData_emptyBatteryEntryList_returnNull() {
         assertThat(DataProcessor.generateBatteryDiffData(mContext,
-                DataProcessor.convertToBatteryHistEntry(null, mBatteryUsageStats))).isNull();
+                System.currentTimeMillis(),
+                DataProcessor.convertToBatteryHistEntry(null, mBatteryUsageStats),
+                /* systemAppsPackageNames= */ Set.of(),
+                /* systemAppsUids= */ Set.of())).isNull();
     }
 
     @Test
@@ -1573,7 +1453,10 @@
                 .when(mMockBatteryEntry4).getPowerComponentId();
 
         final BatteryDiffData batteryDiffData = DataProcessor.generateBatteryDiffData(mContext,
-                DataProcessor.convertToBatteryHistEntry(batteryEntryList, mBatteryUsageStats));
+                System.currentTimeMillis(),
+                DataProcessor.convertToBatteryHistEntry(batteryEntryList, mBatteryUsageStats),
+                /* systemAppsPackageNames= */ Set.of(),
+                /* systemAppsUids= */ Set.of());
 
         assertBatteryDiffEntry(
                 batteryDiffData.getAppDiffEntryList().get(0), 0, /*uid=*/ 2L,
@@ -1644,7 +1527,7 @@
 
         final Map<Long, Map<String, List<AppUsagePeriod>>> appUsagePeriodMap =
                 DataProcessor.buildAppUsagePeriodList(
-                        appUsageEvents, new ArrayList<>(), 0, 5);
+                        mContext, appUsageEvents, new ArrayList<>(), 0, 5);
 
         assertThat(appUsagePeriodMap).hasSize(2);
         final Map<String, List<AppUsagePeriod>> userMap1 = appUsagePeriodMap.get(1L);
@@ -1668,7 +1551,7 @@
     @Test
     public void buildAppUsagePeriodList_emptyEventList_returnNull() {
         assertThat(DataProcessor.buildAppUsagePeriodList(
-                new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull();
+                mContext, new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull();
     }
 
     @Test
@@ -1680,7 +1563,7 @@
                 AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 2));
 
         assertThat(DataProcessor.buildAppUsagePeriodList(
-                appUsageEvents, new ArrayList<>(), 0, 3)).isNull();
+                mContext, appUsageEvents, new ArrayList<>(), 0, 3)).isNull();
     }
 
     @Test
@@ -2040,9 +1923,9 @@
             final double backgroundUsageConsumePower, final double cachedUsageConsumePower,
             final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs,
             final long screenOnTimeInMs) {
-        assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId);
-        assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid);
-        assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(consumerType);
+        assertThat(entry.mUserId).isEqualTo(userId);
+        assertThat(entry.mUid).isEqualTo(uid);
+        assertThat(entry.mConsumerType).isEqualTo(consumerType);
         assertThat(entry.getPercentage()).isEqualTo(consumePercentage);
         assertThat(entry.mForegroundUsageConsumePower).isEqualTo(foregroundUsageConsumePower);
         assertThat(entry.mForegroundServiceUsageConsumePower)
@@ -2053,4 +1936,12 @@
         assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
         assertThat(entry.mScreenOnTimeInMs).isEqualTo(screenOnTimeInMs);
     }
+
+    private BatteryLevelData generateBatteryLevelData(long[] timestamps) {
+        Map<Long, Integer> batteryLevelMap = new ArrayMap<>();
+        for (long timestamp : timestamps) {
+            batteryLevelMap.put(timestamp, 100);
+        }
+        return new BatteryLevelData(batteryLevelMap);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
index efce44e..f72b333 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.fuelgauge.batteryusage;
 
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
+import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -23,15 +26,19 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
+import android.app.usage.IUsageStatsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.database.MatrixCursor;
 import android.os.BatteryManager;
 import android.os.BatteryUsageStats;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 
@@ -67,6 +74,7 @@
     @Mock private BatteryEntry mMockBatteryEntry2;
     @Mock private BatteryEntry mMockBatteryEntry3;
     @Mock private Context mMockContext;
+    @Mock private IUsageStatsManager mUsageStatsManager;
 
     @Before
     public void setUp() {
@@ -77,6 +85,7 @@
         doReturn(mPackageManager).when(mMockContext).getPackageManager();
         doReturn(mPackageManager).when(mContext).getPackageManager();
         DatabaseUtils.getSharedPreferences(mContext).edit().clear().apply();
+        DataProcessor.sUsageStatsManager = mUsageStatsManager;
     }
 
     @Test
@@ -164,8 +173,8 @@
         doReturn(null).when(mContext).registerReceiver(any(), any());
         assertThat(
                 DatabaseUtils.sendBatteryEntryData(
-                        mContext, /*batteryEntryList=*/ null, mBatteryUsageStats,
-                        /*isFullChargeStart=*/ false))
+                        mContext, System.currentTimeMillis(), /*batteryEntryList=*/ null,
+                        mBatteryUsageStats, /*isFullChargeStart=*/ false))
                 .isNull();
     }
 
@@ -184,7 +193,10 @@
 
         final List<ContentValues> valuesList =
                 DatabaseUtils.sendBatteryEntryData(
-                        mContext, batteryEntryList, mBatteryUsageStats,
+                        mContext,
+                        System.currentTimeMillis(),
+                        batteryEntryList,
+                        mBatteryUsageStats,
                         /*isFullChargeStart=*/ false);
 
         assertThat(valuesList).hasSize(2);
@@ -207,6 +219,7 @@
         final List<ContentValues> valuesList =
                 DatabaseUtils.sendBatteryEntryData(
                         mContext,
+                        System.currentTimeMillis(),
                         new ArrayList<>(),
                         mBatteryUsageStats,
                         /*isFullChargeStart=*/ false);
@@ -226,6 +239,7 @@
         final List<ContentValues> valuesList =
                 DatabaseUtils.sendBatteryEntryData(
                         mContext,
+                        System.currentTimeMillis(),
                         /*batteryEntryList=*/ null,
                         mBatteryUsageStats,
                         /*isFullChargeStart=*/ false);
@@ -245,6 +259,7 @@
         final List<ContentValues> valuesList =
                 DatabaseUtils.sendBatteryEntryData(
                         mContext,
+                        System.currentTimeMillis(),
                         /*batteryEntryList=*/ null,
                         /*batteryUsageStats=*/ null,
                         /*isFullChargeStart=*/ false);
@@ -350,7 +365,7 @@
     }
 
     @Test
-    public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() {
+    public void getHistoryMap_emptyCursorContent_returnEmptyMap() {
         final MatrixCursor cursor = new MatrixCursor(
                 new String[] {
                         BatteryHistEntry.KEY_UID,
@@ -358,36 +373,33 @@
                         BatteryHistEntry.KEY_TIMESTAMP});
         DatabaseUtils.sFakeSupplier = () -> cursor;
 
-        assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
-                mContext, /*calendar=*/ null)).isEmpty();
+        assertThat(DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0)).isEmpty();
     }
 
     @Test
-    public void getHistoryMapSinceLastFullCharge_nullCursor_returnEmptyMap() {
+    public void getHistoryMap_nullCursor_returnEmptyMap() {
         DatabaseUtils.sFakeSupplier = () -> null;
-        assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
-                mContext, /*calendar=*/ null)).isEmpty();
+        assertThat(DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0)).isEmpty();
     }
 
     @Test
-    public void getHistoryMapSinceLastFullCharge_returnExpectedMap() {
+    public void getHistoryMap_returnExpectedMap() {
         final Long timestamp1 = Long.valueOf(1001L);
         final Long timestamp2 = Long.valueOf(1002L);
         final MatrixCursor cursor = getMatrixCursor();
         // Adds fake data into the cursor.
         cursor.addRow(new Object[] {
-                "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+                "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, true});
         cursor.addRow(new Object[] {
-                "app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+                "app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, false});
         cursor.addRow(new Object[] {
-                "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+                "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, false});
         cursor.addRow(new Object[] {
-                "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+                "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY, false});
         DatabaseUtils.sFakeSupplier = () -> cursor;
 
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
-                DatabaseUtils.getHistoryMapSinceLastFullCharge(
-                        mContext, /*calendar=*/ null);
+                DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, timestamp1);
 
         assertThat(batteryHistMap).hasSize(2);
         // Verifies the BatteryHistEntry data for timestamp1.
@@ -403,7 +415,7 @@
     }
 
     @Test
-    public void getHistoryMapSinceLastFullCharge_withWorkProfile_returnExpectedMap()
+    public void getHistoryMap_withWorkProfile_returnExpectedMap()
             throws PackageManager.NameNotFoundException {
         doReturn("com.fake.package").when(mContext).getPackageName();
         doReturn(mMockContext).when(mContext).createPackageContextAsUser(
@@ -416,13 +428,77 @@
         DatabaseUtils.sFakeSupplier = () -> getMatrixCursor();
 
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
-                DatabaseUtils.getHistoryMapSinceLastFullCharge(
-                        mContext, /*calendar=*/ null);
+                DatabaseUtils.getHistoryMapSinceQueryTimestamp(mContext, 0);
 
         assertThat(batteryHistMap).isEmpty();
     }
 
     @Test
+    public void removeUsageSource_hasNoData() {
+        DatabaseUtils.removeUsageSource(mContext);
+        assertThat(
+                DatabaseUtils
+                        .getSharedPreferences(mContext)
+                        .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE))
+                .isFalse();
+    }
+
+    @Test
+    public void removeUsageSource_hasData_deleteUsageSource() {
+        final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext);
+        sharedPreferences
+                .edit()
+                .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY)
+                .apply();
+
+        DatabaseUtils.removeUsageSource(mContext);
+
+        assertThat(
+                DatabaseUtils
+                        .getSharedPreferences(mContext)
+                        .contains(DatabaseUtils.KEY_LAST_USAGE_SOURCE))
+                .isFalse();
+    }
+
+    @Test
+    public void getUsageSource_hasData() {
+        final SharedPreferences sharedPreferences = DatabaseUtils.getSharedPreferences(mContext);
+        sharedPreferences
+                .edit()
+                .putInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_TASK_ROOT_ACTIVITY)
+                .apply();
+
+        assertThat(DatabaseUtils.getUsageSource(mContext, mUsageStatsManager))
+                .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+    }
+
+    @Test
+    public void getUsageSource_notHasData_writeLoadedData() throws RemoteException {
+        when(mUsageStatsManager.getUsageSource()).thenReturn(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+
+        assertThat(DatabaseUtils.getUsageSource(mContext, mUsageStatsManager))
+                .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+        assertThat(
+                DatabaseUtils
+                        .getSharedPreferences(mContext)
+                        .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY))
+                .isEqualTo(USAGE_SOURCE_TASK_ROOT_ACTIVITY);
+    }
+
+    @Test
+    public void getUsageSource_throwException_writeDefaultData() throws RemoteException {
+        when(mUsageStatsManager.getUsageSource()).thenThrow(new RemoteException());
+
+        assertThat(DatabaseUtils.getUsageSource(mContext, mUsageStatsManager))
+                .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY);
+        assertThat(
+                DatabaseUtils
+                        .getSharedPreferences(mContext)
+                        .getInt(DatabaseUtils.KEY_LAST_USAGE_SOURCE, USAGE_SOURCE_CURRENT_ACTIVITY))
+                .isEqualTo(USAGE_SOURCE_CURRENT_ACTIVITY);
+    }
+
+    @Test
     public void recordDateTime_writeDataIntoSharedPreferences() {
         final String preferenceKey = "test_preference_key";
         DatabaseUtils.recordDateTime(mContext, preferenceKey);
@@ -497,6 +573,7 @@
                         BatteryHistEntry.KEY_PACKAGE_NAME,
                         BatteryHistEntry.KEY_TIMESTAMP,
                         BatteryHistEntry.KEY_UID,
-                        BatteryHistEntry.KEY_CONSUMER_TYPE});
+                        BatteryHistEntry.KEY_CONSUMER_TYPE,
+                        BatteryHistEntry.KEY_IS_FULL_CHARGE_CYCLE_START});
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java
index 6ed10cd..68766e6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageBaseTest.java
@@ -135,11 +135,6 @@
         }
 
         @Override
-        protected boolean isBatteryHistoryNeeded() {
-            return false;
-        }
-
-        @Override
         protected void refreshUi(int refreshType) {
             // Do nothing
         }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java
new file mode 100644
index 0000000..12c040e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.fuelgauge.batteryusage.bugreport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageLogUtilsTest {
+
+    private StringWriter mTestStringWriter;
+    private PrintWriter mTestPrintWriter;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mTestStringWriter = new StringWriter();
+        mTestPrintWriter = new PrintWriter(mTestStringWriter);
+        BatteryUsageLogUtils.getSharedPreferences(mContext).edit().clear().commit();
+    }
+
+    @Test
+    public void printHistoricalLog_withDefaultLogs() {
+        final String expectedInformation = "nothing to dump";
+        // Environment checking.
+        assertThat(mTestStringWriter.toString().contains(expectedInformation)).isFalse();
+
+        BatteryUsageLogUtils.printHistoricalLog(mContext, mTestPrintWriter);
+        assertThat(mTestStringWriter.toString()).contains(expectedInformation);
+    }
+
+    @Test
+    public void writeLog_multipleLogs_withCorrectCounts() {
+        final int expectedCount = 10;
+        for (int i = 0; i < expectedCount; i++) {
+            BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, "");
+        }
+        BatteryUsageLogUtils.writeLog(mContext, Action.EXECUTE_JOB, "");
+
+        BatteryUsageLogUtils.printHistoricalLog(mContext, mTestPrintWriter);
+
+        assertActionCount("SCHEDULE_JOB", expectedCount);
+        assertActionCount("EXECUTE_JOB", 1);
+    }
+
+    @Test
+    public void writeLog_overMaxEntriesLogs_withCorrectCounts() {
+        BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, "");
+        BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, "");
+        for (int i = 0; i < BatteryUsageLogUtils.MAX_ENTRIES * 2; i++) {
+            BatteryUsageLogUtils.writeLog(mContext, Action.EXECUTE_JOB, "");
+        }
+
+        BatteryUsageLogUtils.printHistoricalLog(mContext, mTestPrintWriter);
+
+        final String dumpResults = mTestStringWriter.toString();
+        assertThat(dumpResults.contains("SCHEDULE_JOB")).isFalse();
+        assertActionCount("EXECUTE_JOB", BatteryUsageLogUtils.MAX_ENTRIES);
+    }
+
+    private void assertActionCount(String token, int count) {
+        final String dumpResults = mTestStringWriter.toString();
+        assertThat(dumpResults.split(token).length).isEqualTo(count + 1);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
index 8365ae4..45d4065 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
@@ -87,6 +87,7 @@
         mBugReportContentProvider.dump(FileDescriptor.out, mPrintWriter, new String[] {});
 
         String dumpContent = mStringWriter.toString();
+        assertThat(dumpContent).contains("Battery PeriodicJob History");
         assertThat(dumpContent).contains("Battery DatabaseHistory");
         assertThat(dumpContent).contains(PACKAGE_NAME1);
         assertThat(dumpContent).contains(PACKAGE_NAME2);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java
index 941f444..8462867 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java
@@ -16,6 +16,10 @@
 
 package com.android.settings.fuelgauge.batteryusage.db;
 
+import static com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity.KEY_BATTERY_EVENT_TYPE;
+import static com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity.KEY_BATTERY_LEVEL;
+import static com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity.KEY_TIMESTAMP;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
@@ -31,9 +35,14 @@
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.List;
+
 /** Tests for {@link BatteryEventDao}. */
 @RunWith(RobolectricTestRunner.class)
 public final class BatteryEventDaoTest {
+    private static final long TIMESTAMP1 = System.currentTimeMillis();
+    private static final long TIMESTAMP2 = TIMESTAMP1 + 2;
+
     private Context mContext;
     private BatteryStateDatabase mDatabase;
     private BatteryEventDao mBatteryEventDao;
@@ -51,8 +60,44 @@
         BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
     }
 
+
     @Test
-    public void getAllAfter_returnExpectedResult() {
+    public void getLastFullChargeTimestamp_normalFlow_expectedBehavior() throws Exception {
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(TIMESTAMP1)
+                .setBatteryEventType(3)
+                .setBatteryLevel(100)
+                .build());
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(TIMESTAMP2)
+                .setBatteryEventType(4)
+                .setBatteryLevel(96)
+                .build());
+
+        final Cursor cursor = mBatteryEventDao.getLastFullChargeTimestamp();
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getLong(0)).isEqualTo(TIMESTAMP1);
+    }
+
+    @Test
+    public void getLastFullChargeTimestamp_noLastFullChargeTime_returns0() throws Exception {
+        mBatteryEventDao.clearAll();
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(TIMESTAMP2)
+                .setBatteryEventType(4)
+                .setBatteryLevel(96)
+                .build());
+
+        final Cursor cursor = mBatteryEventDao.getLastFullChargeTimestamp();
+
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getLong(0)).isEqualTo(0L);
+    }
+
+    @Test
+    public void getAllAfter_normalFlow_returnExpectedResult() {
         mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
                 .setTimestamp(100L)
                 .setBatteryEventType(1)
@@ -64,17 +109,44 @@
                 .setBatteryLevel(88)
                 .build());
 
-        final Cursor cursor = mBatteryEventDao.getAllAfter(160L);
+        final Cursor cursor = mBatteryEventDao.getAllAfter(160L, List.of(1, 2));
         assertThat(cursor.getCount()).isEqualTo(1);
         cursor.moveToFirst();
-        assertThat(cursor.getLong(cursor.getColumnIndex(BatteryEventEntity.KEY_TIMESTAMP)))
+        assertThat(cursor.getLong(cursor.getColumnIndex(KEY_TIMESTAMP)))
                 .isEqualTo(200L);
-        assertThat(cursor.getInt(cursor.getColumnIndex(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE)))
+        assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_EVENT_TYPE)))
                 .isEqualTo(2);
-        assertThat(cursor.getInt(cursor.getColumnIndex(BatteryEventEntity.KEY_BATTERY_LEVEL)))
+        assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_LEVEL)))
                 .isEqualTo(88);
 
         mBatteryEventDao.clearAll();
         assertThat(mBatteryEventDao.getAll()).isEmpty();
     }
+
+    @Test
+    public void getAllAfter_filterBatteryTypes_returnExpectedResult() {
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(100L)
+                .setBatteryEventType(1)
+                .setBatteryLevel(66)
+                .build());
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(200L)
+                .setBatteryEventType(2)
+                .setBatteryLevel(88)
+                .build());
+
+        final Cursor cursor = mBatteryEventDao.getAllAfter(0L, List.of(1));
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getLong(cursor.getColumnIndex(KEY_TIMESTAMP)))
+                .isEqualTo(100L);
+        assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_EVENT_TYPE)))
+                .isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(KEY_BATTERY_LEVEL)))
+                .isEqualTo(66);
+
+        mBatteryEventDao.clearAll();
+        assertThat(mBatteryEventDao.getAll()).isEmpty();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
index 57cf648..b3dba4e 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
@@ -37,9 +37,10 @@
 @RunWith(RobolectricTestRunner.class)
 public final class BatteryStateDaoTest {
     private static final int CURSOR_COLUMN_SIZE = 9;
-    private static final long TIMESTAMP1 = System.currentTimeMillis();
-    private static final long TIMESTAMP2 = System.currentTimeMillis() + 2;
-    private static final long TIMESTAMP3 = System.currentTimeMillis() + 4;
+    private static final long CURRENT = System.currentTimeMillis();
+    private static final long TIMESTAMP1 = CURRENT;
+    private static final long TIMESTAMP2 = CURRENT + 2;
+    private static final long TIMESTAMP3 = CURRENT + 4;
     private static final String PACKAGE_NAME1 = "com.android.apps.settings";
     private static final String PACKAGE_NAME2 = "com.android.apps.calendar";
     private static final String PACKAGE_NAME3 = "com.android.apps.gmail";
@@ -67,7 +68,7 @@
     }
 
     @Test
-    public void batteryStateDao_insertAll() throws Exception {
+    public void insertAll_normalFlow_expectedBehavior() throws Exception {
         final List<BatteryState> states = mBatteryStateDao.getAllAfter(TIMESTAMP1);
         assertThat(states).hasSize(2);
         // Verifies the queried battery states.
@@ -76,8 +77,26 @@
     }
 
     @Test
-    public void batteryStateDao_getCursorSinceLastFullCharge() throws Exception {
-        final Cursor cursor1 = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP1);
+    public void getLatestTimestamp_normalFlow_expectedBehavior() throws Exception {
+        final Cursor cursor1 = mBatteryStateDao.getLatestTimestampBefore(TIMESTAMP1 - 1);
+        assertThat(cursor1.getCount()).isEqualTo(1);
+        cursor1.moveToFirst();
+        assertThat(cursor1.getLong(0)).isEqualTo(0L);
+
+        final Cursor cursor2 = mBatteryStateDao.getLatestTimestampBefore(TIMESTAMP2);
+        assertThat(cursor2.getCount()).isEqualTo(1);
+        cursor2.moveToFirst();
+        assertThat(cursor2.getLong(0)).isEqualTo(TIMESTAMP2);
+
+        final Cursor cursor3 = mBatteryStateDao.getLatestTimestampBefore(TIMESTAMP3 + 1);
+        assertThat(cursor3.getCount()).isEqualTo(1);
+        cursor3.moveToFirst();
+        assertThat(cursor3.getLong(0)).isEqualTo(TIMESTAMP3);
+    }
+
+    @Test
+    public void getBatteryStatesAfter_normalFlow_expectedBehavior() throws Exception {
+        final Cursor cursor1 = mBatteryStateDao.getBatteryStatesAfter(TIMESTAMP1);
         assertThat(cursor1.getCount()).isEqualTo(3);
         assertThat(cursor1.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
         // Verifies the queried first battery state.
@@ -90,7 +109,7 @@
         cursor1.moveToNext();
         assertThat(cursor1.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
 
-        final Cursor cursor2 = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP3);
+        final Cursor cursor2 = mBatteryStateDao.getBatteryStatesAfter(TIMESTAMP3);
         assertThat(cursor2.getCount()).isEqualTo(1);
         assertThat(cursor2.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
         // Verifies the queried first battery state.
@@ -99,25 +118,7 @@
     }
 
     @Test
-    public void batteryStateDao_getCursorSinceLastFullCharge_noFullChargeData_returnSevenDaysData()
-            throws Exception {
-        mBatteryStateDao.clearAll();
-        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP3, PACKAGE_NAME3);
-        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP2, PACKAGE_NAME2);
-        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP1, PACKAGE_NAME1);
-        final Cursor cursor = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP2);
-        assertThat(cursor.getCount()).isEqualTo(2);
-        assertThat(cursor.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
-        // Verifies the queried first battery state.
-        cursor.moveToFirst();
-        assertThat(cursor.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME2);
-        // Verifies the queried third battery state.
-        cursor.moveToNext();
-        assertThat(cursor.getString(3 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
-    }
-
-    @Test
-    public void batteryStateDao_clearAllBefore() throws Exception {
+    public void clearAllBefore_normalFlow_expectedBehavior() throws Exception {
         mBatteryStateDao.clearAllBefore(TIMESTAMP2);
 
         final List<BatteryState> states = mBatteryStateDao.getAllAfter(0);
@@ -127,20 +128,20 @@
     }
 
     @Test
-    public void batteryStateDao_clearAll() throws Exception {
+    public void clearAll_normalFlow_expectedBehavior() throws Exception {
         assertThat(mBatteryStateDao.getAllAfter(0)).hasSize(3);
         mBatteryStateDao.clearAll();
         assertThat(mBatteryStateDao.getAllAfter(0)).isEmpty();
     }
 
     @Test
-    public void getInstance_createNewInstance() throws Exception {
+    public void getInstance_createNewInstance_returnsExpectedResult() throws Exception {
         BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
         assertThat(BatteryStateDatabase.getInstance(mContext)).isNotNull();
     }
 
     @Test
-    public void getDistinctTimestampCount_returnsExpectedResult() {
+    public void getDistinctTimestampCount_normalFlow_returnsExpectedResult() {
         assertThat(mBatteryStateDao.getDistinctTimestampCount(/*timestamp=*/ 0))
                 .isEqualTo(3);
         assertThat(mBatteryStateDao.getDistinctTimestampCount(TIMESTAMP1))
@@ -148,7 +149,7 @@
     }
 
     @Test
-    public void getDistinctTimestamps_returnsExpectedResult() {
+    public void getDistinctTimestamps_normalFlow_returnsExpectedResult() {
         final List<Long> timestamps =
                 mBatteryStateDao.getDistinctTimestamps(/*timestamp=*/ 0);
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java
new file mode 100644
index 0000000..6f73954
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDaoTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.fuelgauge.batteryusage.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.BatteryTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+
+/** Tests for {@link BatteryUsageSlotDao}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageSlotDaoTest {
+    private static final int CURSOR_COLUMN_SIZE = 3;
+    private static final long CURRENT = System.currentTimeMillis();
+    private static final long TIMESTAMP1 = CURRENT;
+    private static final long TIMESTAMP2 = CURRENT + 2;
+    private static final String BATTERY_USAGE_SLOT_STRING1 = "BATTERY_USAGE_SLOT_STRING1";
+    private static final String BATTERY_USAGE_SLOT_STRING2 = "BATTERY_USAGE_SLOT_STRING2";
+
+    private Context mContext;
+    private BatteryStateDatabase mDatabase;
+    private BatteryUsageSlotDao mBatteryUsageSlotDao;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
+        mBatteryUsageSlotDao = mDatabase.batteryUsageSlotDao();
+        mBatteryUsageSlotDao.insert(
+                new BatteryUsageSlotEntity(TIMESTAMP1, BATTERY_USAGE_SLOT_STRING1));
+        mBatteryUsageSlotDao.insert(
+                new BatteryUsageSlotEntity(TIMESTAMP2, BATTERY_USAGE_SLOT_STRING2));
+    }
+
+    @After
+    public void closeDb() {
+        mDatabase.close();
+        BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
+    }
+
+    @Test
+    public void getAll_normalFlow_expectedBehavior() throws Exception {
+        final List<BatteryUsageSlotEntity> entities = mBatteryUsageSlotDao.getAll();
+        assertThat(entities).hasSize(2);
+        assertThat(entities.get(0).timestamp).isEqualTo(TIMESTAMP1);
+        assertThat(entities.get(0).batteryUsageSlot).isEqualTo(BATTERY_USAGE_SLOT_STRING1);
+        assertThat(entities.get(1).timestamp).isEqualTo(TIMESTAMP2);
+        assertThat(entities.get(1).batteryUsageSlot).isEqualTo(BATTERY_USAGE_SLOT_STRING2);
+    }
+
+    @Test
+    public void getAllAfter_normalFlow_expectedBehavior() throws Exception {
+        final Cursor cursor1 = mBatteryUsageSlotDao.getAllAfter(TIMESTAMP1);
+        assertThat(cursor1.getCount()).isEqualTo(2);
+        assertThat(cursor1.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
+        cursor1.moveToFirst();
+        assertThat(cursor1.getLong(1 /*timestamp*/)).isEqualTo(TIMESTAMP1);
+        cursor1.moveToNext();
+        assertThat(cursor1.getLong(1 /*timestamp*/)).isEqualTo(TIMESTAMP2);
+
+        final Cursor cursor2 = mBatteryUsageSlotDao.getAllAfter(TIMESTAMP1 + 1);
+        assertThat(cursor2.getCount()).isEqualTo(1);
+        assertThat(cursor2.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
+        cursor2.moveToFirst();
+        assertThat(cursor2.getLong(1 /*timestamp*/)).isEqualTo(TIMESTAMP2);
+    }
+
+    @Test
+    public void clearAllBefore_normalFlow_expectedBehavior() throws Exception {
+        mBatteryUsageSlotDao.clearAllBefore(TIMESTAMP1);
+
+        final List<BatteryUsageSlotEntity> entities = mBatteryUsageSlotDao.getAll();
+        assertThat(entities).hasSize(1);
+        assertThat(entities.get(0).timestamp).isEqualTo(TIMESTAMP2);
+        assertThat(entities.get(0).batteryUsageSlot).isEqualTo(BATTERY_USAGE_SLOT_STRING2);
+    }
+
+    @Test
+    public void clearAll_normalFlow_expectedBehavior() throws Exception {
+        mBatteryUsageSlotDao.clearAll();
+
+        assertThat(mBatteryUsageSlotDao.getAll()).isEmpty();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java
new file mode 100644
index 0000000..ef276eb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotEntityTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link BatteryUsageSlotEntity}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageSlotEntityTest {
+
+    @Test
+    public void testBuilder_returnsExpectedResult() {
+        final long timestamp = 10001L;
+        final String batteryUsageSlotString = "batteryUsageSlotString";
+
+        BatteryUsageSlotEntity entity = BatteryUsageSlotEntity
+                .newBuilder()
+                .setTimestamp(timestamp)
+                .setBatteryUsageSlot(batteryUsageSlotString)
+                .build();
+
+        // Verifies the app relative information.
+        assertThat(entity.timestamp).isEqualTo(timestamp);
+        assertThat(entity.batteryUsageSlot).isEqualTo(batteryUsageSlotString);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadBottomPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadBottomPreferenceControllerTest.java
index 1b061ec..3c51cf3 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadBottomPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadBottomPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -26,25 +31,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadBottomPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadBottomPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "trackpad_bottom_right_tap";
     private static final String SETTING_KEY = Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE;
 
     private Context mContext;
     private TrackpadBottomPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadBottomPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -70,6 +83,10 @@
                 UserHandle.USER_CURRENT);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_BOTTOM_RIGHT_TAP_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -83,6 +100,10 @@
                 UserHandle.USER_CURRENT);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_BOTTOM_RIGHT_TAP_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceControllerTest.java
index 0e1705e..85d56ef 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoBackPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
@@ -25,25 +30,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadGoBackPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadGoBackPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "gesture_go_back";
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_BACK_ENABLED;
 
     private Context mContext;
     private TrackpadGoBackPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadGoBackPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -65,6 +78,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_GO_BACK_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -74,6 +91,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_GO_BACK_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceControllerTest.java
index 3289bcc..6b3b3f5 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadGoHomePreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
@@ -25,25 +30,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadGoHomePreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadGoHomePreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "gesture_go_home";
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_HOME_ENABLED;
 
     private Context mContext;
     private TrackpadGoHomePreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadGoHomePreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -65,6 +78,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_GO_HOME_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -74,6 +91,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_GO_HOME_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceControllerTest.java
index 3df1627..005bc9f 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadNotificationsPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
@@ -25,25 +30,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadNotificationsPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadNotificationsPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "gesture_notifications";
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_NOTIFICATION_ENABLED;
 
     private Context mContext;
     private TrackpadNotificationsPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadNotificationsPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -65,6 +78,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_NOTIFICATION_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -74,6 +91,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_NOTIFICATION_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceControllerTest.java
index daf1773..1cfda12 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadPointerSpeedPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.hardware.input.InputSettings;
 import android.os.UserHandle;
@@ -26,15 +31,21 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadPointerSpeedPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadPointerSpeedPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "trackpad_pointer_speed";
     private static final String SETTING_KEY = Settings.System.TOUCHPAD_POINTER_SPEED;
@@ -42,10 +53,12 @@
     private Context mContext;
     private TrackpadPointerSpeedPreferenceController mController;
     private int mDefaultSpeed;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadPointerSpeedPreferenceController(mContext, PREFERENCE_KEY);
         mDefaultSpeed = Settings.System.getIntForUser(
                 mContext.getContentResolver(),
@@ -85,6 +98,10 @@
 
         assertThat(result).isTrue();
         assertThat(mController.getSliderPosition()).isEqualTo(inputSpeed);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_POINTER_SPEED_CHANGED),
+                eq(1));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceControllerTest.java
index dbed542..2ef53a6 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadRecentAppsPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
@@ -25,25 +30,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadRecentAppsPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadRecentAppsPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "gesture_recent_apps";
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_OVERVIEW_ENABLED;
 
     private Context mContext;
     private TrackpadRecentAppsPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadRecentAppsPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -65,6 +78,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_RECENT_APPS_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -74,6 +91,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_RECENT_APPS_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java
index a99abb8..e74261e 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadReverseScrollingPreferenceControllerTest.java
@@ -16,9 +16,13 @@
 
 package com.android.settings.inputmethod;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -27,25 +31,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadReverseScrollingPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadReverseScrollingPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "trackpad_reverse_scrolling";
     private static final String SETTING_KEY = Settings.System.TOUCHPAD_NATURAL_SCROLLING;
 
     private Context mContext;
     private TrackpadReverseScrollingPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadReverseScrollingPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -71,6 +83,10 @@
                 UserHandle.USER_CURRENT);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_REVERSE_SCROLLING_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -84,6 +100,10 @@
                 UserHandle.USER_CURRENT);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_REVERSE_SCROLLING_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceControllerTest.java
index 3f16025..5e354d2 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadSwitchAppsPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.provider.Settings;
 
@@ -25,25 +30,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadSwitchAppsPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadSwitchAppsPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "gesture_switch_apps";
     private static final String SETTING_KEY = Settings.Secure.TRACKPAD_GESTURE_QUICK_SWITCH_ENABLED;
 
     private Context mContext;
     private TrackpadSwitchAppsPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadSwitchAppsPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -65,6 +78,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_SWITCH_APPS_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -74,6 +91,10 @@
         int result = Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 1);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_SWITCH_APPS_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceControllerTest.java
index b4b8921..3784cc7 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TrackpadTapToClickPreferenceControllerTest.java
@@ -18,6 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -26,25 +31,33 @@
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
 /** Tests for {@link TrackpadTapToClickPreferenceController} */
 @RunWith(RobolectricTestRunner.class)
 public class TrackpadTapToClickPreferenceControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final String PREFERENCE_KEY = "trackpad_tap_to_click";
     private static final String SETTING_KEY = Settings.System.TOUCHPAD_TAP_TO_CLICK;
 
     private Context mContext;
     private TrackpadTapToClickPreferenceController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
         mController = new TrackpadTapToClickPreferenceController(mContext, PREFERENCE_KEY);
     }
 
@@ -70,6 +83,10 @@
                 UserHandle.USER_CURRENT);
 
         assertThat(result).isEqualTo(1);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_TAP_TO_CLICK_CHANGED),
+                eq(true));
     }
 
     @Test
@@ -83,6 +100,10 @@
                 UserHandle.USER_CURRENT);
 
         assertThat(result).isEqualTo(0);
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(),
+                eq(SettingsEnums.ACTION_GESTURE_TAP_TO_CLICK_CHANGED),
+                eq(false));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
index 48caecd..8fb3a5d 100644
--- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -27,6 +29,7 @@
 import android.app.Activity;
 import android.app.ApplicationPackageManager;
 import android.app.LocaleConfig;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -45,6 +48,7 @@
 import com.android.internal.app.LocaleStore;
 import com.android.settings.applications.AppInfoBase;
 import com.android.settings.applications.AppLocaleUtil;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.After;
 import org.junit.Before;
@@ -79,6 +83,7 @@
 public class AppLocalePickerActivityTest {
     private static final String TEST_PACKAGE_NAME = "com.android.settings";
     private static final Uri TEST_PACKAGE_URI = Uri.parse("package:" + TEST_PACKAGE_NAME);
+    private FakeFeatureFactory mFeatureFactory;
 
     @Mock
     LocaleStore.LocaleInfo mLocaleInfo;
@@ -99,6 +104,7 @@
         when(mLocaleConfig.getStatus()).thenReturn(LocaleConfig.STATUS_SUCCESS);
         when(mLocaleConfig.getSupportedLocales()).thenReturn(LocaleList.forLanguageTags("en-US"));
         ReflectionHelpers.setStaticField(AppLocaleUtil.class, "sLocaleConfig", mLocaleConfig);
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
     }
 
     @After
@@ -210,6 +216,37 @@
         assertThat(controller.get().isFinishing()).isTrue();
     }
 
+    @Test
+    public void onLocaleSelected_logLocaleSource() {
+        ActivityController<TestAppLocalePickerActivity> controller =
+                initActivityController(true);
+        LocaleList.setDefault(LocaleList.forLanguageTags("ja-JP,en-CA,en-US"));
+        Locale locale = new Locale("en", "US");
+        when(mLocaleInfo.getLocale()).thenReturn(locale);
+        when(mLocaleInfo.isSystemLocale()).thenReturn(false);
+        when(mLocaleInfo.isSuggested()).thenReturn(true);
+        when(mLocaleInfo.isSuggestionOfType(LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)).thenReturn(
+                true);
+        when(mLocaleInfo.isSuggestionOfType(
+                LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)).thenReturn(
+                true);
+        when(mLocaleInfo.isSuggestionOfType(
+                LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)).thenReturn(
+                true);
+        when(mLocaleInfo.isSuggestionOfType(
+                LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)).thenReturn(
+                true);
+
+        controller.create();
+        AppLocalePickerActivity mActivity = controller.get();
+        mActivity.onLocaleSelected(mLocaleInfo);
+
+        int localeSource = 15; // SIM_LOCALE | SYSTEM_LOCALE |IME_LOCALE|APP_LOCALE
+        verify(mFeatureFactory.metricsFeatureProvider).action(
+                any(), eq(SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED),
+                eq(localeSource));
+    }
+
     private ActivityController<TestAppLocalePickerActivity> initActivityController(
             boolean hasPackageName) {
         Intent data = new Intent();
diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java
new file mode 100644
index 0000000..57f2b01
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.localepicker;
+
+import static com.android.settings.localepicker.LocaleDialogFragment.ARG_DIALOG_TYPE;
+import static com.android.settings.localepicker.LocaleDialogFragment.ARG_TARGET_LOCALE;
+import static com.android.settings.localepicker.LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.window.OnBackInvokedDispatcher;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+import com.android.internal.app.LocaleStore;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.utils.ActivityControllerWrapper;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Locale;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowAlertDialogCompat.class})
+public class LocaleDialogFragmentTest {
+
+    @Mock
+    private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+
+    private FragmentActivity mActivity;
+    private LocaleDialogFragment mDialogFragment;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mActivity = (FragmentActivity) ActivityControllerWrapper.setup(
+                Robolectric.buildActivity(FragmentActivity.class)).get();
+        mDialogFragment = LocaleDialogFragment.newInstance();
+        LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(Locale.ENGLISH);
+        Bundle args = new Bundle();
+        args.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
+        args.putSerializable(ARG_TARGET_LOCALE, localeInfo);
+        mDialogFragment.setArguments(args);
+        FragmentManager fragmentManager = mActivity.getSupportFragmentManager();
+        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+        fragmentTransaction.add(mDialogFragment, null);
+        fragmentTransaction.commit();
+    }
+
+    @Test
+    public void onCreateDialog_onBackInvokedCallbackIsRegistered() {
+        mDialogFragment.setBackDispatcher(mOnBackInvokedDispatcher);
+        mDialogFragment.onCreateDialog(null);
+
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any());
+    }
+
+    @Test
+    public void onBackInvoked_dialogIsStillDisplaying() {
+        mDialogFragment.setBackDispatcher(mOnBackInvokedDispatcher);
+        AlertDialog alertDialog = (AlertDialog) mDialogFragment.onCreateDialog(null);
+        alertDialog.show();
+        assertThat(alertDialog).isNotNull();
+        assertThat(alertDialog.isShowing()).isTrue();
+
+        mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any());
+
+        mDialogFragment.getBackInvokedCallback().onBackInvoked();
+
+        assertThat(alertDialog.isShowing()).isTrue();
+
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
index 16d51be..5ff2baf 100644
--- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -27,12 +28,17 @@
 import android.app.Activity;
 import android.app.IActivityManager;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.appcompat.app.AlertDialog;
@@ -45,6 +51,7 @@
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.ShadowActivityManager;
 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 
 import org.junit.After;
 import org.junit.Before;
@@ -73,11 +80,12 @@
     private static final int REQUEST_CONFIRM_SYSTEM_DEFAULT = 1;
 
     private LocaleListEditor mLocaleListEditor;
-
     private Context mContext;
     private FragmentActivity mActivity;
-    private List mLocaleList;
+    private List<LocaleStore.LocaleInfo> mLocaleList;
     private Intent mIntent = new Intent();
+    private LocaleDragCell mLocaleDragCell;
+    private LocaleDragAndDropAdapter.CustomViewHolder mCustomViewHolder;
 
     @Mock
     private LocaleDragAndDropAdapter mAdapter;
@@ -91,11 +99,25 @@
     private View mView;
     @Mock
     private IActivityManager mActivityService;
+    @Mock
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+    @Mock
+    private TextView mLabel;
+    @Mock
+    private CheckBox mCheckbox;
+    @Mock
+    private TextView mMiniLabel;
+    @Mock
+    private TextView mLocalized;
+    @Mock
+    private TextView mCurrentDefault;
+    @Mock
+    private ImageView mDragHandle;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
+        mContext = spy(RuntimeEnvironment.application);
         mLocaleListEditor = spy(new LocaleListEditor());
         when(mLocaleListEditor.getContext()).thenReturn(mContext);
         mActivity = Robolectric.buildActivity(FragmentActivity.class).get();
@@ -108,6 +130,8 @@
                 RuntimeEnvironment.application.getSystemService(Context.USER_SERVICE));
         ReflectionHelpers.setField(mLocaleListEditor, "mAdapter", mAdapter);
         ReflectionHelpers.setField(mLocaleListEditor, "mFragmentManager", mFragmentManager);
+        ReflectionHelpers.setField(mLocaleListEditor, "mMetricsFeatureProvider",
+                mMetricsFeatureProvider);
         when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
         FakeFeatureFactory.setupForTest();
     }
@@ -200,6 +224,38 @@
     }
 
     @Test
+    public void showConfirmDialog_systemLocaleSelected_shouldShowLocaleChangeDialog()
+            throws Exception {
+        //pre-condition
+        setUpLocaleConditions();
+        final Configuration config = new Configuration();
+        config.setLocales((LocaleList.forLanguageTags("zh-TW,en-US")));
+        when(mActivityService.getConfiguration()).thenReturn(config);
+        when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
+        when(mAdapter.getCheckedCount()).thenReturn(1);
+        when(mAdapter.getItemCount()).thenReturn(2);
+        when(mAdapter.isFirstLocaleChecked()).thenReturn(true);
+        ReflectionHelpers.setField(mLocaleListEditor, "mRemoveMode", true);
+        ReflectionHelpers.setField(mLocaleListEditor, "mShowingRemoveDialog", true);
+
+        //launch the first dialog
+        mLocaleListEditor.showRemoveLocaleWarningDialog();
+
+        final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+
+        assertThat(dialog).isNotNull();
+
+        // click the remove button
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+        assertThat(dialog.isShowing()).isFalse();
+
+        // check the second dialog is showing
+        verify(mFragmentTransaction).add(any(LocaleDialogFragment.class),
+                eq(TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT));
+    }
+
+    @Test
     public void mayAppendUnicodeTags_appendUnicodeTags_success() {
         LocaleStore.LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag("en-US"));
 
@@ -262,6 +318,35 @@
         verify(mAdapter).doTheUpdate();
     }
 
+    @Test
+    public void onBindViewHolder_shouldSetCheckedBoxText() {
+        ReflectionHelpers.setField(mLocaleListEditor, "mRemoveMode", true);
+        mLocaleList = new ArrayList<>();
+        mLocaleList.add(mLocaleInfo);
+        when(mLocaleInfo.getFullNameNative()).thenReturn("English");
+        when(mLocaleInfo.getLocale()).thenReturn(LocaleList.forLanguageTags("en-US").get(0));
+
+        mAdapter = spy(new LocaleDragAndDropAdapter(mLocaleListEditor, mLocaleList));
+        ReflectionHelpers.setField(mAdapter, "mFeedItemList", mLocaleList);
+        ReflectionHelpers.setField(mAdapter, "mParent", mLocaleListEditor);
+        ReflectionHelpers.setField(mAdapter, "mCacheItemList", new ArrayList<>(mLocaleList));
+        ReflectionHelpers.setField(mAdapter, "mContext", mContext);
+        ViewGroup view = new FrameLayout(mContext);
+        mCustomViewHolder = mAdapter.onCreateViewHolder(view, 0);
+        mLocaleDragCell = new LocaleDragCell(mContext, null);
+        ReflectionHelpers.setField(mCustomViewHolder, "mLocaleDragCell", mLocaleDragCell);
+        ReflectionHelpers.setField(mLocaleDragCell, "mLabel", mLabel);
+        ReflectionHelpers.setField(mLocaleDragCell, "mLocalized", mLocalized);
+        ReflectionHelpers.setField(mLocaleDragCell, "mCurrentDefault", mCurrentDefault);
+        ReflectionHelpers.setField(mLocaleDragCell, "mMiniLabel", mMiniLabel);
+        ReflectionHelpers.setField(mLocaleDragCell, "mDragHandle", mDragHandle);
+        ReflectionHelpers.setField(mLocaleDragCell, "mCheckbox", mCheckbox);
+
+        mAdapter.onBindViewHolder(mCustomViewHolder, 0);
+
+        verify(mAdapter).setCheckBoxDescription(any(LocaleDragCell.class), any(), anyBoolean());
+    }
+
     private void setUpLocaleConditions() {
         ShadowActivityManager.setService(mActivityService);
         mLocaleList = new ArrayList<>();
diff --git a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
index 59f0bcb..47bf99d 100644
--- a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java
@@ -17,62 +17,81 @@
 package com.android.settings.notification;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doCallRealMethod;
 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.res.Configuration;
+import android.content.res.Resources;
 import android.media.AudioManager;
+import android.os.LocaleList;
 import android.preference.SeekBarVolumizer;
 import android.widget.SeekBar;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Locale;
+
 @RunWith(RobolectricTestRunner.class)
 public class VolumeSeekBarPreferenceTest {
 
     private static final CharSequence CONTENT_DESCRIPTION = "TEST";
+    private static final int STREAM = 5;
     @Mock
     private AudioManager mAudioManager;
     @Mock
     private VolumeSeekBarPreference mPreference;
     @Mock
     private Context mContext;
+
+    @Mock
+    private Resources mRes;
+    @Mock
+    private Configuration mConfig;
     @Mock
     private SeekBar mSeekBar;
+    @Captor
+    private ArgumentCaptor<SeekBarVolumizer.Callback> mSbvc;
     @Mock
     private SeekBarVolumizer mVolumizer;
+    @Mock
+    private SeekBarVolumizerFactory mSeekBarVolumizerFactory;
     private VolumeSeekBarPreference.Listener mListener;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mSeekBarVolumizerFactory.create(eq(STREAM), eq(null), mSbvc.capture()))
+                .thenReturn(mVolumizer);
+        doCallRealMethod().when(mPreference).setStream(anyInt());
         doCallRealMethod().when(mPreference).updateContentDescription(CONTENT_DESCRIPTION);
         mPreference.mSeekBar = mSeekBar;
         mPreference.mAudioManager = mAudioManager;
-        mPreference.mVolumizer = mVolumizer;
+        mPreference.mSeekBarVolumizerFactory = mSeekBarVolumizerFactory;
         mListener = () -> mPreference.updateContentDescription(CONTENT_DESCRIPTION);
     }
 
     @Test
     public void setStream_shouldSetMinMaxAndProgress() {
-        final int stream = 5;
         final int max = 17;
         final int min = 1;
         final int progress = 4;
-        when(mAudioManager.getStreamMaxVolume(stream)).thenReturn(max);
-        when(mAudioManager.getStreamMinVolumeInt(stream)).thenReturn(min);
-        when(mAudioManager.getStreamVolume(stream)).thenReturn(progress);
-        doCallRealMethod().when(mPreference).setStream(anyInt());
+        when(mAudioManager.getStreamMaxVolume(STREAM)).thenReturn(max);
+        when(mAudioManager.getStreamMinVolumeInt(STREAM)).thenReturn(min);
+        when(mAudioManager.getStreamVolume(STREAM)).thenReturn(progress);
 
-        mPreference.setStream(stream);
+        mPreference.setStream(STREAM);
 
         verify(mPreference).setMax(max);
         verify(mPreference).setMin(min);
@@ -84,6 +103,7 @@
         doCallRealMethod().when(mPreference).setListener(mListener);
         doCallRealMethod().when(mPreference).init();
 
+        mPreference.setStream(STREAM);
         mPreference.setListener(mListener);
         mPreference.init();
 
@@ -94,8 +114,69 @@
     public void init_listenerNotSet_noException() {
         doCallRealMethod().when(mPreference).init();
 
+        mPreference.setStream(STREAM);
         mPreference.init();
 
         verify(mPreference, never()).updateContentDescription(CONTENT_DESCRIPTION);
     }
+
+    @Test
+    public void init_changeProgress_overrideStateDescriptionCalled() {
+        final int progress = 4;
+        when(mPreference.formatStateDescription(progress)).thenReturn(CONTENT_DESCRIPTION);
+        doCallRealMethod().when(mPreference).init();
+
+        mPreference.setStream(STREAM);
+        mPreference.init();
+
+        verify(mSeekBarVolumizerFactory).create(eq(STREAM), eq(null), mSbvc.capture());
+
+        mSbvc.getValue().onProgressChanged(mSeekBar, 4, true);
+
+        verify(mPreference).overrideSeekBarStateDescription(CONTENT_DESCRIPTION);
+    }
+
+    @Test
+    public void init_changeProgress_stateDescriptionValueUpdated() {
+        final int max = 17;
+        final int min = 1;
+        int progress = 4;
+        when(mAudioManager.getStreamMaxVolume(STREAM)).thenReturn(max);
+        when(mAudioManager.getStreamMinVolumeInt(STREAM)).thenReturn(min);
+        when(mAudioManager.getStreamVolume(STREAM)).thenReturn(progress);
+        when(mPreference.getMin()).thenReturn(min);
+        when(mPreference.getMax()).thenReturn(max);
+        when(mPreference.getContext()).thenReturn(mContext);
+        when(mContext.getResources()).thenReturn(mRes);
+        when(mRes.getConfiguration()).thenReturn(mConfig);
+        when(mConfig.getLocales()).thenReturn(new LocaleList(Locale.US));
+        doCallRealMethod().when(mPreference).init();
+
+        mPreference.setStream(STREAM);
+        mPreference.init();
+
+        // On progress change, Round down the percent to match it with what the talkback says.
+        // (b/285458191)
+        // when progress is 4, the percent is 0.187. The state description should be set to 18%.
+        testFormatStateDescription(progress, "18%");
+
+        progress = 6;
+
+        // when progress is 6, the percent is 0.3125. The state description should be set to 31%.
+        testFormatStateDescription(progress, "31%");
+
+        progress = 7;
+
+        // when progress is 7, the percent is 0.375. The state description should be set to 37%.
+        testFormatStateDescription(progress, "37%");
+    }
+
+    private void testFormatStateDescription(int progress, String expected) {
+        doCallRealMethod().when(mPreference).formatStateDescription(progress);
+        doCallRealMethod().when(mPreference).getPercent(progress);
+
+        mSbvc.getValue().onProgressChanged(mSeekBar, progress, true);
+
+        verify(mPreference).overrideSeekBarStateDescription(expected);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
index 12a540d..5db998a 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
@@ -60,6 +60,7 @@
 import com.android.internal.widget.LockscreenCredential;
 import com.android.settings.R;
 import com.android.settings.biometrics.BiometricEnrollBase;
+import com.android.settings.biometrics.BiometricUtils;
 import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
 import com.android.settings.search.SearchFeatureProvider;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -543,29 +544,38 @@
     }
 
     @Test
-    public void updatePreferenceText_supportBiometrics_showFaceAndFingerprint() {
+    public void updatePreferenceText_supportBiometrics_setScreenLockFingerprintFace_inOrder() {
         ShadowStorageManager.setIsFileEncrypted(false);
         final Intent intent = new Intent().putExtra(EXTRA_KEY_FOR_BIOMETRICS, true);
         initActivity(intent);
 
-
         final String supportFingerprint = capitalize(mActivity.getResources().getString(
                 R.string.security_settings_fingerprint));
         final String supportFace = capitalize(mActivity.getResources().getString(
                 R.string.keywords_face_settings));
-        String pinTitle =
+
+        // The strings of golden copy
+        final String pinFingerprintFace = mActivity.getText(R.string.unlock_set_unlock_pin_title)
+                + BiometricUtils.SEPARATOR + supportFingerprint + BiometricUtils.SEPARATOR
+                + supportFace;
+        final String patternFingerprintFace = mActivity.getText(
+                R.string.unlock_set_unlock_pattern_title) + BiometricUtils.SEPARATOR
+                + supportFingerprint + BiometricUtils.SEPARATOR + supportFace;
+        final String passwordFingerprintFace = mActivity.getText(
+                R.string.unlock_set_unlock_password_title) + BiometricUtils.SEPARATOR
+                + supportFingerprint + BiometricUtils.SEPARATOR + supportFace;
+
+        // The strings obtain from preferences
+        final String pinTitle =
                 (String) mFragment.findPreference(ScreenLockType.PIN.preferenceKey).getTitle();
-        String patternTitle =
+        final String patternTitle =
                 (String) mFragment.findPreference(ScreenLockType.PATTERN.preferenceKey).getTitle();
-        String passwordTitle =
+        final String passwordTitle =
                 (String) mFragment.findPreference(ScreenLockType.PASSWORD.preferenceKey).getTitle();
 
-        assertThat(pinTitle).contains(supportFingerprint);
-        assertThat(pinTitle).contains(supportFace);
-        assertThat(patternTitle).contains(supportFingerprint);
-        assertThat(patternTitle).contains(supportFace);
-        assertThat(passwordTitle).contains(supportFingerprint);
-        assertThat(passwordTitle).contains(supportFace);
+        assertThat(pinTitle).isEqualTo(pinFingerprintFace);
+        assertThat(patternTitle).isEqualTo(patternFingerprintFace);
+        assertThat(passwordTitle).isEqualTo(passwordFingerprintFace);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
index 3fe3322..feea768 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
@@ -27,10 +27,8 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 
-import static com.android.internal.widget.LockPatternUtils.FLAG_ENABLE_AUTO_PIN_CONFIRMATION;
 import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
 import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS;
 
@@ -45,7 +43,6 @@
 import android.app.admin.PasswordPolicy;
 import android.content.Intent;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.view.View;
 import android.widget.CheckBox;
 import android.widget.TextView;
@@ -55,7 +52,6 @@
 import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
 import com.android.settings.password.ChooseLockPassword.IntentBuilder;
 import com.android.settings.testutils.shadow.SettingsShadowResources;
-import com.android.settings.testutils.shadow.ShadowDeviceConfig;
 import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
 import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
 import com.android.settings.testutils.shadow.ShadowUtils;
@@ -65,7 +61,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
@@ -74,14 +69,12 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowDrawable;
 
-@Ignore
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {
         SettingsShadowResources.class,
         ShadowLockPatternUtils.class,
         ShadowUtils.class,
         ShadowDevicePolicyManager.class,
-        ShadowDeviceConfig.class,
 })
 public class ChooseLockPasswordTest {
     @Before
@@ -397,24 +390,7 @@
     }
 
     @Test
-    public void processAndValidatePasswordRequirements_autoPinDisabled_defaultPinMinimumLength() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* value= */ "false", /* makeDefault= */ false);
-        PasswordPolicy policy = new PasswordPolicy();
-        policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
-
-        assertPasswordValidationResult(
-                /* minMetrics */ policy.getMinMetrics(),
-                /* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
-                /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
-                /* userEnteredPassword= */ LockscreenCredential.createPassword("11"),
-                "PIN must be at least 4 digits");
-    }
-
-    @Test
-    public void processAndValidatePasswordRequirements_autoPinEnabled_defaultPinMinimumLength() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* value= */ "true", /* makeDefault= */ false);
+    public void processAndValidatePasswordRequirements_defaultPinMinimumLength() {
         PasswordPolicy policy = new PasswordPolicy();
         policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
 
@@ -454,8 +430,6 @@
 
     @Test
     public void autoPinConfirmOption_featureEnabledAndUntouchedByUser_changeStateAsPerRules() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* value= */ "true", /* makeDefault= */ false);
         ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
 
         ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
@@ -492,8 +466,6 @@
 
     @Test
     public void autoPinConfirmOption_featureEnabledAndModifiedByUser_shouldChangeStateAsPerRules() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* value= */ "true", /* makeDefault= */ false);
         ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
 
         ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
@@ -525,38 +497,6 @@
         assertThat(pinAutoConfirmOption.isChecked()).isFalse();
     }
 
-    @Test
-    public void autoPinConfirmOption_featureDisabled_shouldRemainInvisibleAndUnchecked() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                /* value= */ "false", /* makeDefault= */ false);
-        ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
-
-        ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
-        ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
-        CheckBox pinAutoConfirmOption = passwordActivity
-                .findViewById(R.id.auto_pin_confirm_enabler);
-        TextView securityMessage =
-                passwordActivity.findViewById(R.id.auto_pin_confirm_security_message);
-
-        passwordEntry.setText("1234");
-        fragment.updateUi();
-        assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
-        assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
-        assertThat(pinAutoConfirmOption.isChecked()).isFalse();
-
-        passwordEntry.setText("123456");
-        fragment.updateUi();
-        assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
-        assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
-        assertThat(pinAutoConfirmOption.isChecked()).isFalse();
-
-        passwordEntry.setText("12345678");
-        fragment.updateUi();
-        assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
-        assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
-        assertThat(pinAutoConfirmOption.isChecked()).isFalse();
-    }
-
     private ChooseLockPassword setupActivityWithPinTypeAndDefaultPolicy() {
         PasswordPolicy policy = new PasswordPolicy();
         policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
diff --git a/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java
index a3e2ed4..8bccf1a 100644
--- a/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/SetupChooseLockPasswordTest.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
+import android.widget.LinearLayout;
 
 import androidx.appcompat.app.AlertDialog;
 
@@ -107,6 +108,20 @@
     }
 
     @Test
+    public void createActivity_withShowOptionsButtonExtra_shouldShowButtonUnderSudHeader() {
+        SetupChooseLockPassword activity = createSetupChooseLockPassword();
+        final LinearLayout headerLayout = activity.findViewById(
+                R.id.sud_layout_header);
+        assertThat(headerLayout).isNotNull();
+
+        final Button optionsButton = headerLayout.findViewById(R.id.screen_lock_options);
+        assertThat(optionsButton).isNotNull();
+
+        optionsButton.performClick();
+        assertThat(ShadowDialog.getLatestDialog()).isNotNull();
+    }
+
+    @Test
     @Config(shadows = ShadowChooseLockGenericController.class)
     public void createActivity_withShowOptionsButtonExtra_buttonNotVisibleIfNoVisibleLockTypes() {
         SetupChooseLockPassword activity = createSetupChooseLockPassword();
diff --git a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
index 715913c..86c1244 100644
--- a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java
@@ -16,22 +16,16 @@
 
 package com.android.settings.security.screenlock;
 
-import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
-
-import static com.android.internal.widget.LockPatternUtils.FLAG_ENABLE_AUTO_PIN_CONFIRMATION;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.provider.DeviceConfig;
 
 import androidx.preference.SwitchPreference;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.testutils.shadow.ShadowDeviceConfig;
 import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
 
 import org.junit.Before;
@@ -40,10 +34,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowDeviceConfig.class})
 public class AutoPinConfirmPreferenceControllerTest {
     private static final Integer TEST_USER_ID = 1;
     @Mock
@@ -65,8 +57,6 @@
 
     @Test
     public void isAvailable_featureEnabledAndLockSetToNone_shouldReturnFalse() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                "true", /* makeDefault */ false);
         when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
 
         assertThat(mController.isAvailable()).isFalse();
@@ -74,8 +64,6 @@
 
     @Test
     public void isAvailable_featureEnabledAndLockSetToPassword_shouldReturnFalse() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                "true", /* makeDefault */ false);
         when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
         when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
                 .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
@@ -85,8 +73,6 @@
 
     @Test
     public void isAvailable_featureEnabledAndLockSetToPIN_lengthLessThanSix_shouldReturnFalse() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                "true", /* makeDefault */ false);
         when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
                 .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
         when(mLockPatternUtils.getPinLength(TEST_USER_ID)).thenReturn(5);
@@ -96,8 +82,6 @@
 
     @Test
     public void isAvailable_featureEnabledAndLockSetToPIN_lengthMoreThanEqSix_shouldReturnTrue() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                "true", /* makeDefault */ false);
         when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
         when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
                 .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
@@ -107,20 +91,7 @@
     }
 
     @Test
-    public void isAvailable_featureDisabledAndLockSetToPIN_shouldReturnFalse() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                "false", /* makeDefault */ false);
-        when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
-        when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
-                .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
     public void updateState_ChangingSettingState_shouldSetPreferenceToAppropriateCheckedState() {
-        DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
-                "true", /* makeDefault */ false);
         // When auto_pin_confirm setting is disabled, switchPreference is unchecked
         when(mLockPatternUtils.isAutoPinConfirmEnabled(TEST_USER_ID)).thenReturn(false);
         mController.updateState(mPreference);
diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
index b7d249d..4903a28 100644
--- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
+++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
@@ -119,6 +119,7 @@
     private Context mContext;
     private SettingsSliceProvider mProvider;
     private ShadowPackageManager mPackageManager;
+    private ShadowUserManager mShadowUserManager;
 
     @Mock
     private SliceManager mManager;
@@ -157,6 +158,7 @@
         when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList());
 
         mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+        mShadowUserManager = ShadowUserManager.getShadow();
 
         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
     }
@@ -293,6 +295,37 @@
     }
 
     @Test
+    public void onBindSlice_guestRestricted_returnsNull() {
+        final String key = "enable_usb_tethering";
+        mShadowUserManager.setGuestUser(true);
+        final Uri testUri = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath(key)
+            .build();
+
+        final Slice slice = mProvider.onBindSlice(testUri);
+
+        assertThat(slice).isNull();
+    }
+
+    @Test
+    public void onBindSlice_notGuestRestricted_returnsNotNull() {
+        final String key = "enable_usb_tethering";
+        final Uri testUri = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath(key)
+            .build();
+
+        final Slice slice = mProvider.onBindSlice(testUri);
+
+        assertThat(slice).isNotNull();
+    }
+
+    @Test
     public void getDescendantUris_fullActionUri_returnsSelf() {
         final Collection<Uri> descendants = mProvider.onGetSliceDescendants(ACTION_SLICE_URI);
 
diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
index c54b750..77be038 100644
--- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.when;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.usb.UsbManager;
@@ -25,11 +26,19 @@
 import android.hardware.usb.UsbPortStatus;
 import android.os.BatteryManager;
 import android.os.UserManager;
+
 import androidx.room.Room;
 
+import com.android.settings.display.AutoBrightnessSettings;
+import com.android.settings.display.ScreenTimeoutSettings;
 import com.android.settings.fuelgauge.batteryusage.BatteryInformation;
 import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
 import com.android.settings.fuelgauge.batteryusage.DeviceBatteryState;
+import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEvent;
+import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
+import com.android.settings.fuelgauge.batteryusage.PowerAnomalyKey;
+import com.android.settings.fuelgauge.batteryusage.PowerAnomalyType;
+import com.android.settings.fuelgauge.batteryusage.WarningBannerInfo;
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventDao;
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
@@ -193,4 +202,33 @@
         when(mockUsbPortStatus.getComplianceWarnings())
                 .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_OTHER});
     }
+
+    /** Create an empty power anomaly event list proto. */
+    public static PowerAnomalyEventList createEmptyPowerAnomalyEventList() {
+        return PowerAnomalyEventList.getDefaultInstance();
+    }
+
+    /** Create a power anomaly event proto of adaptive brightness. */
+    public static PowerAnomalyEvent createAdaptiveBrightnessAnomalyEvent() {
+        return PowerAnomalyEvent.newBuilder()
+                .setType(PowerAnomalyType.TYPE_SETTINGS_BANNER)
+                .setKey(PowerAnomalyKey.KEY_BRIGHTNESS)
+                .setWarningBannerInfo(WarningBannerInfo.newBuilder()
+                        .setMainButtonDestination(AutoBrightnessSettings.class.getName())
+                        .setMainButtonSourceMetricsCategory(SettingsEnums.SETTINGS_AUTO_BRIGHTNESS)
+                        .build())
+                .build();
+    }
+
+    /** Create a power anomaly event proto of screen timeout. */
+    public static PowerAnomalyEvent createScreenTimeoutAnomalyEvent() {
+        return PowerAnomalyEvent.newBuilder()
+                .setType(PowerAnomalyType.TYPE_SETTINGS_BANNER)
+                .setKey(PowerAnomalyKey.KEY_SCREEN_TIMEOUT)
+                .setWarningBannerInfo(WarningBannerInfo.newBuilder()
+                        .setMainButtonDestination(ScreenTimeoutSettings.class.getName())
+                        .setMainButtonSourceMetricsCategory(SettingsEnums.SCREEN_TIMEOUT)
+                        .build())
+                .build();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
index 29a6da3..5891aa1 100644
--- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -29,6 +29,7 @@
 import com.android.settings.biometrics.face.FaceFeatureProvider;
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
 import com.android.settings.bluetooth.BluetoothFeatureProvider;
+import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
 import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider;
@@ -39,6 +40,7 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.gestures.AssistGestureFeatureProvider;
 import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
+import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.overlay.DockUpdaterFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
@@ -95,6 +97,8 @@
     public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
     public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
     public WifiFeatureProvider mWifiFeatureProvider;
+    public KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
+    public StylusFeatureProvider mStylusFeatureProvider;
 
     /**
      * Call this in {@code @Before} method of the test class to use fake factory.
@@ -147,6 +151,8 @@
         mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class);
         mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class);
         mWifiFeatureProvider = mock(WifiFeatureProvider.class);
+        mKeyboardSettingsFeatureProvider = mock(KeyboardSettingsFeatureProvider.class);
+        mStylusFeatureProvider = mock(StylusFeatureProvider.class);
     }
 
     @Override
@@ -170,7 +176,7 @@
     }
 
     @Override
-    public BatterySettingsFeatureProvider getBatterySettingsFeatureProvider(Context context) {
+    public BatterySettingsFeatureProvider getBatterySettingsFeatureProvider() {
         return batterySettingsFeatureProvider;
     }
 
@@ -303,4 +309,14 @@
     public WifiFeatureProvider getWifiFeatureProvider() {
         return mWifiFeatureProvider;
     }
+
+    @Override
+    public KeyboardSettingsFeatureProvider getKeyboardSettingsFeatureProvider() {
+        return mKeyboardSettingsFeatureProvider;
+    }
+
+    @Override
+    public StylusFeatureProvider getStylusFeatureProvider() {
+        return mStylusFeatureProvider;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java
index df38420..324a829 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java
@@ -55,6 +55,7 @@
     private int[] profileIdsForUser = new int[0];
     private boolean mUserSwitchEnabled;
     private Bundle mDefaultGuestUserRestriction = new Bundle();
+    private boolean mIsGuestUser = false;
 
     private @UserManager.UserSwitchabilityResult int mSwitchabilityStatus =
             UserManager.SWITCHABILITY_STATUS_OK;
@@ -270,4 +271,13 @@
             mUserProfileInfos.get(i).flags |= UserInfo.FLAG_ADMIN;
         }
     }
+
+    @Implementation
+    protected boolean isGuestUser() {
+        return mIsGuestUser;
+    }
+
+    public void setGuestUser(boolean isGuestUser) {
+        mIsGuestUser = isGuestUser;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/users/GuestTelephonyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/users/GuestTelephonyPreferenceControllerTest.java
index aa84cb6..c4b514c 100644
--- a/tests/robotests/src/com/android/settings/users/GuestTelephonyPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/users/GuestTelephonyPreferenceControllerTest.java
@@ -18,12 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.Answers.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.SystemProperties;
 import android.os.UserManager;
 
@@ -103,6 +105,8 @@
 
     @Test
     public void updateState_Admin_shouldDisplayPreference() {
+        assumeTrue("Device does not have telephony feature ",
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
         SystemProperties.set("fw.max_users", Long.toBinaryString(4));
         mDpm.setDeviceOwner(null);
         mUserManager.setIsAdminUser(true);
diff --git a/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java
index efc2018..457d9ab 100644
--- a/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java
@@ -18,6 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -60,7 +64,7 @@
         when(mWifiEntry.canDisconnect()).thenReturn(false);
         when(mWifiEntry.isSaved()).thenReturn(false);
 
-        mPreference = new LongPressWifiEntryPreference(mContext, mWifiEntry, mFragment);
+        mPreference = spy(new LongPressWifiEntryPreference(mContext, mWifiEntry, mFragment));
     }
 
     @Test
@@ -106,4 +110,23 @@
 
         assertThat(mPreference.shouldEnabled()).isTrue();
     }
+
+    @Test
+    public void checkRestrictionAndSetDisabled_hasAdminRestrictions_doSetDisabledByAdmin() {
+        when(mContext.getUser()).thenReturn(null);
+        when(mWifiEntry.hasAdminRestrictions()).thenReturn(true);
+
+        mPreference.checkRestrictionAndSetDisabled();
+
+        verify(mPreference).setDisabledByAdmin(any());
+    }
+
+    @Test
+    public void checkRestrictionAndSetDisabled_noAdminRestrictions_doNotSetDisabledByAdmin() {
+        when(mWifiEntry.hasAdminRestrictions()).thenReturn(false);
+
+        mPreference.checkRestrictionAndSetDisabled();
+
+        verify(mPreference, never()).setDisabledByAdmin(any());
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java
index a60b531..316beb3 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java
@@ -18,11 +18,15 @@
 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.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -31,6 +35,7 @@
 
 import com.android.settingslib.R;
 import com.android.settingslib.wifi.WifiUtils;
+import com.android.wifitrackerlib.HotspotNetworkEntry;
 import com.android.wifitrackerlib.WifiEntry;
 
 import org.junit.Before;
@@ -52,6 +57,8 @@
     @Mock
     private WifiEntry mMockWifiEntry;
     @Mock
+    private HotspotNetworkEntry mHotspotNetworkEntry;
+    @Mock
     private WifiUtils.InternetIconInjector mMockIconInjector;
 
     @Mock
@@ -256,4 +263,26 @@
     public void getSecondTargetResId_shouldNotReturnZero() {
         assertThat(mPref.getSecondTargetResId()).isNotEqualTo(0);
     }
+
+    @Test
+    public void refresh_itsHotspotNetworkEntry_shouldUpdateHotspotIcon() {
+        int deviceType = NetworkProviderInfo.DEVICE_TYPE_PHONE;
+        when(mHotspotNetworkEntry.getDeviceType()).thenReturn(deviceType);
+        WifiEntryPreference pref = spy(
+                new WifiEntryPreference(mContext, mHotspotNetworkEntry, mMockIconInjector));
+
+        pref.refresh();
+
+        verify(pref).updateHotspotIcon(deviceType);
+    }
+
+    @Test
+    public void refresh_notHotspotNetworkEntry_shouldNotUpdateHotspotIcon() {
+        WifiEntryPreference pref = spy(
+                new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector));
+
+        pref.refresh();
+
+        verify(pref, never()).updateHotspotIcon(anyInt());
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
index c86a023..dcd9b36 100644
--- a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
@@ -701,10 +701,10 @@
     }
 
     @Test
-    public void linkSpeedPref_shouldNotShowIfNotSet() {
+    public void linkSpeedPref_shouldNotShowIfSpeedStringIsEmpty() {
         setUpForConnectedNetwork();
         setUpSpyController();
-        when(mMockWifiInfo.getTxLinkSpeedMbps()).thenReturn(WifiInfo.LINK_SPEED_UNKNOWN);
+        when(mMockWifiEntry.getTxSpeedString()).thenReturn("");
 
         displayAndResume();
 
@@ -712,42 +712,22 @@
     }
 
     @Test
-    public void linkSpeedPref_shouldVisibleForConnectedNetwork() {
+    public void linkSpeedPref_shouldBeVisibleIfSpeedStringIsNotEmpty() {
         setUpForConnectedNetwork();
         setUpSpyController();
-        String expectedLinkSpeed = mContext.getString(R.string.tx_link_speed, TX_LINK_SPEED);
+        when(mMockWifiEntry.getTxSpeedString()).thenReturn("100 Mbps");
 
         displayAndResume();
 
         verify(mMockTxLinkSpeedPref).setVisible(true);
-        verify(mMockTxLinkSpeedPref).setSummary(expectedLinkSpeed);
+        verify(mMockTxLinkSpeedPref).setSummary("100 Mbps");
     }
 
     @Test
-    public void linkSpeedPref_shouldInvisibleForDisconnectedNetwork() {
-        setUpForDisconnectedNetwork();
-
-        displayAndResume();
-
-        verify(mMockTxLinkSpeedPref).setVisible(false);
-        verify(mMockTxLinkSpeedPref, never()).setSummary(any(String.class));
-    }
-
-    @Test
-    public void linkSpeedPref_shouldInvisibleForNotInRangeNetwork() {
-        setUpForNotInRangeNetwork();
-
-        displayAndResume();
-
-        verify(mMockTxLinkSpeedPref).setVisible(false);
-        verify(mMockTxLinkSpeedPref, never()).setSummary(any(String.class));
-    }
-
-    @Test
-    public void rxLinkSpeedPref_shouldNotShowIfNotSet() {
+    public void rxLinkSpeedPref_shouldNotShowIfSpeedStringIsEmpty() {
         setUpForConnectedNetwork();
         setUpSpyController();
-        when(mMockWifiInfo.getRxLinkSpeedMbps()).thenReturn(WifiInfo.LINK_SPEED_UNKNOWN);
+        when(mMockWifiEntry.getRxSpeedString()).thenReturn("");
 
         displayAndResume();
 
@@ -755,35 +735,15 @@
     }
 
     @Test
-    public void rxLinkSpeedPref_shouldVisibleForConnectedNetwork() {
+    public void rxLinkSpeedPref_shouldBeVisibleIfSpeedStringIsNotEmpty() {
         setUpForConnectedNetwork();
         setUpSpyController();
-        String expectedLinkSpeed = mContext.getString(R.string.rx_link_speed, RX_LINK_SPEED);
+        when(mMockWifiEntry.getRxSpeedString()).thenReturn("100 Mbps");
 
         displayAndResume();
 
         verify(mMockRxLinkSpeedPref).setVisible(true);
-        verify(mMockRxLinkSpeedPref).setSummary(expectedLinkSpeed);
-    }
-
-    @Test
-    public void rxLinkSpeedPref_shouldInvisibleForDisconnectedNetwork() {
-        setUpForDisconnectedNetwork();
-
-        displayAndResume();
-
-        verify(mMockRxLinkSpeedPref).setVisible(false);
-        verify(mMockRxLinkSpeedPref, never()).setSummary(any(String.class));
-    }
-
-    @Test
-    public void rxLinkSpeedPref_shouldInvisibleForNotInRangeNetwork() {
-        setUpForNotInRangeNetwork();
-
-        displayAndResume();
-
-        verify(mMockRxLinkSpeedPref).setVisible(false);
-        verify(mMockRxLinkSpeedPref, never()).setSummary(any(String.class));
+        verify(mMockRxLinkSpeedPref).setSummary("100 Mbps");
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java
index fbe184d..25a59a9 100644
--- a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -151,6 +152,13 @@
     }
 
     @Test
+    public void onDeviceInfoAvailable_nullChannel_shouldBeIgnored() {
+        mFragment.sChannel = null;
+        mFragment.onDeviceInfoAvailable(mock(WifiP2pDevice.class));
+        verify(mWifiP2pManager, never()).requestNetworkInfo(any(), any());
+    }
+
+    @Test
     public void beSearching_getP2pStateDisabledIntent_shouldBeFalse() {
         final Bundle bundle = new Bundle();
         final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
index 75d49fe..e67717d 100644
--- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
@@ -23,6 +23,7 @@
 
 import static com.android.settings.wifi.WifiUtils.setCanShowWifiHotspotCached;
 import static com.android.settings.wifi.repository.WifiHotspotRepository.BAND_2GHZ_5GHZ_6GHZ;
+import static com.android.settings.wifi.tether.WifiTetherSettings.KEY_INSTANT_HOTSPOT;
 import static com.android.settings.wifi.tether.WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY;
 import static com.android.settings.wifi.tether.WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED;
 
@@ -90,6 +91,7 @@
     private static final String[] WIFI_REGEXS = {"wifi_regexs"};
     private static final String SSID = "ssid";
     private static final String PASSWORD = "password";
+    private static final String SUMMARY = "summary";
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -133,6 +135,10 @@
     private WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController;
     @Mock
     private WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController;
+    @Mock
+    private Preference mInstantHotspot;
+    @Mock
+    private LiveData<String> mInstantHotspotSummary;
 
     private WifiTetherSettings mSettings;
 
@@ -155,8 +161,10 @@
         when(provider.getWifiTetherViewModel(mock(ViewModelStoreOwner.class)))
                 .thenReturn(mWifiTetherViewModel);
         when(mWifiTetherViewModel.isSpeedFeatureAvailable()).thenReturn(false);
+        when(mWifiTetherViewModel.isInstantHotspotFeatureAvailable()).thenReturn(true);
         when(mWifiTetherViewModel.getSecuritySummary()).thenReturn(mSecuritySummary);
         when(mWifiTetherViewModel.getSpeedSummary()).thenReturn(mSpeedSummary);
+        when(mWifiTetherViewModel.getInstantHotspotSummary()).thenReturn(mInstantHotspotSummary);
 
         mSettings = spy(new WifiTetherSettings(mWifiRestriction));
         mSettings.mMainSwitchBar = mMainSwitchBar;
@@ -172,6 +180,8 @@
         mSettings.mWifiTetherViewModel = mWifiTetherViewModel;
         when(mSettings.findPreference(KEY_WIFI_HOTSPOT_SECURITY)).thenReturn(mWifiHotspotSecurity);
         when(mSettings.findPreference(KEY_WIFI_HOTSPOT_SPEED)).thenReturn(mWifiHotspotSpeed);
+        when(mSettings.findPreference(KEY_INSTANT_HOTSPOT)).thenReturn(mInstantHotspot);
+        mSettings.mInstantHotspot = mInstantHotspot;
     }
 
     @Test
@@ -373,6 +383,47 @@
     }
 
     @Test
+    public void setupInstantHotspot_featureNotAvailable_doNothing() {
+        mSettings.setupInstantHotspot(false /* isFeatureAvailable */);
+
+        verify(mSettings, never()).findPreference(KEY_INSTANT_HOTSPOT);
+        verify(mWifiTetherViewModel, never()).getInstantHotspotSummary();
+    }
+
+    @Test
+    public void setupInstantHotspot_featureAvailable_doSetup() {
+        when(mWifiTetherViewModel.isInstantHotspotFeatureAvailable()).thenReturn(true);
+
+        mSettings.setupInstantHotspot(true /* isFeatureAvailable */);
+
+        verify(mSettings).findPreference(KEY_INSTANT_HOTSPOT);
+        verify(mInstantHotspotSummary).observe(any(), any());
+        verify(mInstantHotspot).setOnPreferenceClickListener(any());
+    }
+
+    @Test
+    public void onInstantHotspotChanged_nullRecord_setVisibleFalse() {
+        mSettings.onInstantHotspotChanged(null);
+
+        verify(mInstantHotspot).setVisible(false);
+    }
+
+    @Test
+    public void onInstantHotspotChanged_summaryNull_setVisibleFalse() {
+        mSettings.onInstantHotspotChanged(null);
+
+        verify(mInstantHotspot).setVisible(false);
+    }
+
+    @Test
+    public void onInstantHotspotChanged_summaryNotNull_setVisibleAndSummary() {
+        mSettings.onInstantHotspotChanged(SUMMARY);
+
+        verify(mInstantHotspot).setVisible(true);
+        verify(mInstantHotspot).setSummary(SUMMARY);
+    }
+
+    @Test
     public void buildNewConfig_speedFeatureIsAvailableAndPasswordChanged_bandShouldNotBeLost() {
         String newPassword = "new" + PASSWORD;
         SoftApConfiguration currentConfig = new SoftApConfiguration.Builder()
diff --git a/tests/spa_unit/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.kt b/tests/spa_unit/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.kt
new file mode 100644
index 0000000..c2413af
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/applications/specialaccess/DataSaverControllerTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.specialaccess
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.res.Resources
+import android.net.NetworkPolicyManager
+import android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settings.applications.specialaccess.DataSaverController.Companion.getUnrestrictedSummary
+import com.android.settings.core.BasePreferenceController.AVAILABLE
+import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
+import com.android.settingslib.spaprivileged.model.app.AppListRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class DataSaverControllerTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Spy
+    private val resources: Resources = context.resources
+
+    @Mock
+    private lateinit var networkPolicyManager: NetworkPolicyManager
+
+    @Mock
+    private lateinit var dataSaverController: DataSaverController
+
+    @Before
+    fun setUp() {
+        whenever(context.applicationContext).thenReturn(context)
+        whenever(context.resources).thenReturn(resources)
+        whenever(NetworkPolicyManager.from(context)).thenReturn(networkPolicyManager)
+
+        dataSaverController = DataSaverController(context, "key")
+    }
+
+    @Test
+    fun getAvailabilityStatus_whenConfigOn_available() {
+        whenever(resources.getBoolean(R.bool.config_show_data_saver)).thenReturn(true)
+        assertThat(dataSaverController.availabilityStatus).isEqualTo(AVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_whenConfigOff_unsupportedOnDevice() {
+        whenever(resources.getBoolean(R.bool.config_show_data_saver)).thenReturn(false)
+        assertThat(dataSaverController.availabilityStatus).isEqualTo(UNSUPPORTED_ON_DEVICE)
+    }
+
+    @Test
+    fun getUnrestrictedSummary_whenTwoAppsAllowed() = runTest {
+        whenever(
+            networkPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)
+        ).thenReturn(intArrayOf(APP1.uid, APP2.uid))
+
+        val summary =
+            getUnrestrictedSummary(context = context, appListRepository = FakeAppListRepository)
+
+        assertThat(summary)
+            .isEqualTo("2 apps allowed to use unrestricted data when Data Saver is on")
+    }
+
+    @Test
+    fun getUnrestrictedSummary_whenNoAppsAllowed() = runTest {
+        whenever(
+            networkPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)
+        ).thenReturn(intArrayOf())
+
+        val summary =
+            getUnrestrictedSummary(context = context, appListRepository = FakeAppListRepository)
+
+        assertThat(summary)
+            .isEqualTo("0 apps allowed to use unrestricted data when Data Saver is on")
+    }
+
+    private companion object {
+        val APP1 = ApplicationInfo().apply { uid = 10001 }
+        val APP2 = ApplicationInfo().apply { uid = 10002 }
+        val APP3 = ApplicationInfo().apply { uid = 10003 }
+
+        object FakeAppListRepository : AppListRepository {
+            override suspend fun loadApps(
+                userId: Int,
+                loadInstantApps: Boolean,
+                matchAnyUserForAdmin: Boolean,
+            ) = emptyList<ApplicationInfo>()
+
+            override fun showSystemPredicate(
+                userIdFlow: Flow<Int>,
+                showSystemFlow: Flow<Boolean>,
+            ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { false }
+
+            override fun getSystemPackageNamesBlocking(userId: Int): Set<String> = emptySet()
+
+            override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) =
+                listOf(APP1, APP2, APP3)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataUsageFormatterTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataUsageFormatterTest.kt
new file mode 100644
index 0000000..dc6a421
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/datausage/DataUsageFormatterTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.datausage
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.datausage.DataUsageFormatter.getBytesDisplayUnit
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class DataUsageFormatterTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun getUnitDisplayName_megaByte() {
+        val displayName = context.resources.getBytesDisplayUnit(ONE_MEGA_BYTE_IN_BYTES)
+
+        assertThat(displayName).isEqualTo("MB")
+    }
+
+    @Test
+    fun getUnitDisplayName_gigaByte() {
+        val displayName = context.resources.getBytesDisplayUnit(ONE_GIGA_BYTE_IN_BYTES)
+
+        assertThat(displayName).isEqualTo("GB")
+    }
+
+    private companion object {
+        const val ONE_MEGA_BYTE_IN_BYTES = 1024L * 1024
+        const val ONE_GIGA_BYTE_IN_BYTES = 1024L * 1024 * 1024
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyStatusControlSessionTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyStatusControlSessionTest.kt
new file mode 100644
index 0000000..7e6a91b
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/TelephonyStatusControlSessionTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.spa.testutils.waitUntil
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class TelephonyStatusControlSessionTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun init() = runTest {
+        val controller = TestController(context)
+
+        val session = TelephonyStatusControlSession(
+            controllers = listOf(controller),
+            lifecycle = TestLifecycleOwner().lifecycle,
+        )
+
+        waitUntil { controller.availabilityStatus == STATUS }
+        session.close()
+    }
+
+    @Test
+    fun close() = runTest {
+        val controller = TestController(context)
+
+        val session = TelephonyStatusControlSession(
+            controllers = listOf(controller),
+            lifecycle = TestLifecycleOwner().lifecycle,
+        )
+        session.close()
+
+        assertThat(controller.availabilityStatus).isNull()
+    }
+
+    private companion object {
+        const val KEY = "key"
+        const val STATUS = BasePreferenceController.AVAILABLE
+    }
+
+    private class TestController(context: Context) : BasePreferenceController(context, KEY),
+        TelephonyAvailabilityHandler {
+
+        var availabilityStatus: Int? = null
+        override fun getAvailabilityStatus(): Int = STATUS
+
+        override fun setAvailabilityStatus(status: Int) {
+            availabilityStatus = status
+        }
+
+        override fun unsetAvailabilityStatus() {
+            availabilityStatus = null
+        }
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt
new file mode 100644
index 0000000..0235a7b
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreferenceTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appcompat
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER
+import android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasTextExactly
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settings.applications.appcompat.UserAspectRatioDetails
+import com.android.settings.applications.appcompat.UserAspectRatioManager
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settings.testutils.TestDeviceConfig
+import com.android.settingslib.spa.testutils.delay
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * To run this test: atest SettingsSpaUnitTests:UserAspectRatioAppPreferenceTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserAspectRatioAppPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private lateinit var mockSession: MockitoSession
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Spy
+    private val resources = context.resources
+
+    private val aspectRatioEnabledConfig =
+        TestDeviceConfig(NAMESPACE_WINDOW_MANAGER, "enable_app_compat_user_aspect_ratio_settings")
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    @Before
+    fun setUp() {
+        mockSession = ExtendedMockito.mockitoSession()
+            .initMocks(this)
+            .mockStatic(UserAspectRatioDetails::class.java)
+            .mockStatic(AppInfoDashboardFragment::class.java)
+            .strictness(Strictness.LENIENT)
+            .startMocking()
+        whenever(context.resources).thenReturn(resources)
+        whenever(context.packageManager).thenReturn(packageManager)
+    }
+
+    @After
+    fun tearDown() {
+        aspectRatioEnabledConfig.reset()
+        mockSession.finishMocking()
+    }
+
+    @Test
+    fun whenConfigIsFalse_notDisplayed() {
+        setConfig(false)
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun whenCannotDisplayAspectRatioUi_notDisplayed() {
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun whenCanDisplayAspectRatioUiAndConfigFalse_notDisplayed() {
+        setConfig(false)
+        whenever(packageManager.queryIntentActivities(any(), anyInt()))
+            .thenReturn(listOf(RESOLVE_INFO))
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun whenCannotDisplayAspectRatioUiAndConfigTrue_notDisplayed() {
+        // True is ignored but need this here or getBoolean will complain null object
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, true)
+        setConfig(true)
+
+        setContent()
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun whenCanDisplayAspectRatioUiAndConfigTrue_Displayed() {
+        // True is ignored but need this here or getBoolean will complain null object
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, true)
+        setConfig(true)
+        whenever(packageManager.queryIntentActivities(any(), anyInt()))
+            .thenReturn(listOf(RESOLVE_INFO))
+
+        setContent()
+
+        composeTestRule.onNode(
+            hasTextExactly(
+                context.getString(R.string.aspect_ratio_title),
+                context.getString(R.string.user_aspect_ratio_app_default)
+            ),
+        ).assertIsDisplayed().assertIsEnabled()
+    }
+
+    @Test
+    fun onClick_startActivity() {
+        // True is ignored but need this here or getBoolean will complain null object
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, true)
+        setConfig(true)
+        whenever(packageManager.queryIntentActivities(any(), anyInt()))
+            .thenReturn(listOf(RESOLVE_INFO))
+
+        setContent()
+        composeTestRule.onRoot().performClick()
+
+        ExtendedMockito.verify {
+            AppInfoDashboardFragment.startAppInfoFragment(
+                UserAspectRatioDetails::class.java,
+                APP,
+                context,
+                AppInfoSettingsProvider.METRICS_CATEGORY,
+            )
+        }
+    }
+
+    private fun setConfig(enabled: Boolean) {
+        whenever(resources.getBoolean(
+            com.android.internal.R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled
+        )).thenReturn(enabled)
+        aspectRatioEnabledConfig.override(enabled)
+    }
+
+    private fun setContent() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                UserAspectRatioAppPreference(APP)
+            }
+        }
+        composeTestRule.delay()
+    }
+
+    private fun mockProperty(propertyName: String, value: Boolean) {
+        val prop = PackageManager.Property(
+            propertyName, value, PACKAGE_NAME, "" /* className */)
+        whenever(packageManager.getProperty(propertyName, PACKAGE_NAME)).thenReturn(prop)
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "com.test.mypackage"
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+        }
+        private val RESOLVE_INFO = ResolveInfo().apply {
+            activityInfo = ActivityInfo().apply {
+                packageName = PACKAGE_NAME
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
new file mode 100644
index 0000000..c314655
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.appcompat
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN
+import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+import android.os.Build
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * To run this test: atest SettingsSpaUnitTests:UserAspectRatioAppsPageProviderTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UserAspectRatioAppsPageProviderTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+    @Test
+    fun aspectRatioAppsPageProvider_name() {
+        assertThat(UserAspectRatioAppsPageProvider.name).isEqualTo(EXPECTED_PROVIDER_NAME)
+    }
+
+    @Test
+    fun injectEntry_title() {
+        setInjectEntry()
+        composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun injectEntry_summary() {
+        setInjectEntry()
+        composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_summary, Build.MODEL))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun injectEntry_onClick_navigate() {
+        setInjectEntry()
+        composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title)).performClick()
+        assertThat(fakeNavControllerWrapper.navigateCalledWith).isEqualTo("UserAspectRatioAppsPage")
+    }
+
+    private fun setInjectEntry() {
+        composeTestRule.setContent {
+            fakeNavControllerWrapper.Wrapper {
+                UserAspectRatioAppsPageProvider.buildInjectEntry().build().UiLayout()
+            }
+        }
+    }
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            UserAspectRatioAppList {}
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.aspect_ratio_title))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun item_labelDisplayed() {
+        setItemContent()
+
+        composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun aspectRatioAppListModel_transform() = runTest {
+        val listModel = UserAspectRatioAppListModel(context)
+        val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP)))
+        val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+
+        assertThat(recordList).hasSize(1)
+        assertThat(recordList[0].app).isSameInstanceAs(APP)
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun aspectRatioAppListModel_filter() = runTest {
+        val listModel = UserAspectRatioAppListModel(context)
+
+        val recordListFlow = listModel.filter(flowOf(USER_ID), 0,
+            flowOf(listOf(APP_RECORD_NOT_DISPLAYED, APP_RECORD_SUGGESTED)))
+
+        val recordList = checkNotNull(recordListFlow.firstWithTimeoutOrNull())
+        assertThat(recordList).containsExactly(APP_RECORD_SUGGESTED)
+    }
+
+    private fun setItemContent() {
+        composeTestRule.setContent {
+            fakeNavControllerWrapper.Wrapper {
+                with(UserAspectRatioAppListModel(context)) {
+                    AppListItemModel(
+                        record = APP_RECORD_SUGGESTED,
+                        label = LABEL,
+                        summary = stateOf(SUMMARY)
+                    ).AppItem()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun aspectRatioAppListModel_getSummaryDefault() {
+        val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_UNSET)
+        assertThat(summaryState.value)
+            .isEqualTo(context.getString(R.string.user_aspect_ratio_app_default))
+    }
+
+    @Test
+    fun aspectRatioAppListModel_getSummaryWhenSplitScreen() {
+        val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN)
+        assertThat(summaryState.value)
+            .isEqualTo(context.getString(R.string.user_aspect_ratio_half_screen))
+    }
+
+    private fun setSummaryState(userOverride: Int): State<String> {
+        val listModel = UserAspectRatioAppListModel(context)
+        lateinit var summaryState: State<String>
+        composeTestRule.setContent {
+            summaryState = listModel.getSummary(option = 0,
+                record = UserAspectRatioAppListItemModel(
+                    app = APP,
+                    userOverride = userOverride,
+                    suggested = false,
+                    canDisplay = true,
+                ))
+        }
+        return summaryState
+    }
+
+
+    private companion object {
+        private const val EXPECTED_PROVIDER_NAME = "UserAspectRatioAppsPage"
+        private const val PACKAGE_NAME = "package.name"
+        private const val USER_ID = 0
+        private const val LABEL = "Label"
+        private const val SUMMARY = "Summary"
+
+        private val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+        private val APP_RECORD_SUGGESTED = UserAspectRatioAppListItemModel(
+            APP,
+            userOverride = USER_MIN_ASPECT_RATIO_UNSET,
+            suggested = true,
+            canDisplay = true
+        )
+        private val APP_RECORD_NOT_DISPLAYED = UserAspectRatioAppListItemModel(
+            APP,
+            userOverride = USER_MIN_ASPECT_RATIO_UNSET,
+            suggested = true,
+            canDisplay = false
+        )
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt
new file mode 100644
index 0000000..78aca85
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.development.compat
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PackageInfoFlags
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class PlatformCompatAppListModelTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private lateinit var listModel: PlatformCompatAppListModel
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getInstalledPackagesAsUser(any<PackageInfoFlags>(), anyInt()))
+            .thenReturn(emptyList())
+        listModel = PlatformCompatAppListModel(context)
+    }
+
+    @Test
+    fun transform() = runTest {
+        val recordListFlow = listModel.transform(
+            userIdFlow = flowOf(USER_ID),
+            appListFlow = flowOf(listOf(APP)),
+        )
+
+        val recordList = recordListFlow.first()
+        assertThat(recordList).hasSize(1)
+        val record = recordList[0]
+        assertThat(record.app).isSameInstanceAs(APP)
+    }
+
+    @Test
+    fun getSummary() = runTest {
+        val summaryState = getSummaryState(APP)
+
+        assertThat(summaryState.value).isEqualTo(PACKAGE_NAME)
+    }
+
+    private fun getSummaryState(app: ApplicationInfo): State<String> {
+        lateinit var summary: State<String>
+        composeTestRule.setContent {
+            summary = listModel.getSummary(
+                option = 0,
+                record = PlatformCompatAppRecord(app),
+            )
+        }
+        return summary
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val PACKAGE_NAME = "package.name"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 99d4f32..6320fc7 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -25,6 +25,7 @@
 import com.android.settings.biometrics.face.FaceFeatureProvider
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
 import com.android.settings.bluetooth.BluetoothFeatureProvider
+import com.android.settings.connecteddevice.stylus.StylusFeatureProvider
 import com.android.settings.dashboard.DashboardFeatureProvider
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider
 import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider
@@ -34,6 +35,7 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider
 import com.android.settings.gestures.AssistGestureFeatureProvider
 import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider
+import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider
 import com.android.settings.localepicker.LocaleFeatureProvider
 import com.android.settings.overlay.DockUpdaterFeatureProvider
 import com.android.settings.overlay.FeatureFactory
@@ -84,9 +86,7 @@
         TODO("Not yet implemented")
     }
 
-    override fun getBatterySettingsFeatureProvider(
-        context: Context?,
-    ): BatterySettingsFeatureProvider {
+    override fun getBatterySettingsFeatureProvider(): BatterySettingsFeatureProvider {
         TODO("Not yet implemented")
     }
 
@@ -187,4 +187,12 @@
     override fun getWifiFeatureProvider(): WifiFeatureProvider {
         TODO("Not yet implemented")
     }
+
+    override fun getKeyboardSettingsFeatureProvider(): KeyboardSettingsFeatureProvider {
+        TODO("Not yet implemented")
+    }
+
+    override fun getStylusFeatureProvider(): StylusFeatureProvider {
+        TODO("Not yet implemented")
+    }
 }
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 8e81218..82a488d 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -21,6 +21,7 @@
     ],
 
     static_libs: [
+	"androidx.arch.core_core-testing",
         "androidx.test.core",
         "androidx.test.rules",
         "androidx.test.espresso.core",
@@ -32,6 +33,7 @@
         "platform-test-annotations",
         "truth-prebuilt",
         "androidx.test.uiautomator_uiautomator",
+        "kotlinx_coroutines_test",
         // Don't add SettingsLib libraries here - you can use them directly as they are in the
         // instrumented Settings app.
     ],
@@ -40,8 +42,11 @@
         javacflags: ["-Xep:CheckReturnValue:WARN"]
     },
 
-    // Include all test java files.
-    srcs: ["src/**/*.java"],
+    // Include all test java/kotlin files.
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
 
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java b/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java
new file mode 100644
index 0000000..6cc386b
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/appcompat/UserAspectRatioManagerTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.appcompat;
+
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
+
+import static com.android.settings.applications.appcompat.UserAspectRatioManager.KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN;
+import static com.android.settings.applications.appcompat.UserAspectRatioManager.KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * To run this test: atest SettingsUnitTests:UserAspectRatioManagerTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class UserAspectRatioManagerTest {
+
+    private Context mContext;
+    private Resources mResources;
+    private UserAspectRatioManager mUtils;
+    private String mOriginalSettingsFlag;
+    private String mOriginalFullscreenFlag;
+    private String mPackageName = "com.test.mypackage";
+
+    @Before
+    public void setUp() {
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        mResources = spy(mContext.getResources());
+        mUtils = new UserAspectRatioManager(mContext);
+
+        when(mContext.getResources()).thenReturn(mResources);
+
+        mOriginalSettingsFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS);
+        setAspectRatioSettingsBuildTimeFlagEnabled(true);
+        setAspectRatioSettingsDeviceConfigEnabled("true" /* enabled */, false /* makeDefault */);
+
+        mOriginalFullscreenFlag = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN);
+        setAspectRatioFullscreenBuildTimeFlagEnabled(true);
+        setAspectRatioFullscreenDeviceConfigEnabled("true" /* enabled */, false /* makeDefault */);
+    }
+
+    @After
+    public void tearDown() {
+        setAspectRatioSettingsDeviceConfigEnabled(mOriginalSettingsFlag, true /* makeDefault */);
+        setAspectRatioFullscreenDeviceConfigEnabled(mOriginalFullscreenFlag,
+                true /* makeDefault */);
+    }
+
+    @Test
+    public void testCanDisplayAspectRatioUi() {
+        final ApplicationInfo canDisplay = new ApplicationInfo();
+        canDisplay.packageName = "com.app.candisplay";
+        addResolveInfoLauncherEntry(canDisplay.packageName);
+
+        assertTrue(mUtils.canDisplayAspectRatioUi(canDisplay));
+
+        final ApplicationInfo noLauncherEntry = new ApplicationInfo();
+        noLauncherEntry.packageName = "com.app.nolauncherentry";
+
+        assertFalse(mUtils.canDisplayAspectRatioUi(noLauncherEntry));
+    }
+
+    @Test
+    public void testCanDisplayAspectRatioUi_hasLauncher_propertyFalse_returnFalse()
+            throws PackageManager.NameNotFoundException {
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, false);
+
+        final ApplicationInfo canDisplay = new ApplicationInfo();
+        canDisplay.packageName = mPackageName;
+        addResolveInfoLauncherEntry(canDisplay.packageName);
+
+        assertFalse(mUtils.canDisplayAspectRatioUi(canDisplay));
+    }
+
+    @Test
+    public void testCanDisplayAspectRatioUi_noLauncher_propertyTrue_returnFalse()
+            throws PackageManager.NameNotFoundException {
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, true);
+
+        final ApplicationInfo noLauncherEntry = new ApplicationInfo();
+        noLauncherEntry.packageName = mPackageName;
+
+        assertFalse(mUtils.canDisplayAspectRatioUi(noLauncherEntry));
+    }
+
+    @Test
+    public void testIsFeatureEnabled() {
+        assertTrue(UserAspectRatioManager.isFeatureEnabled(mContext));
+    }
+
+    @Test
+    public void testIsFeatureEnabled_disabledBuildTimeFlag_returnFalse() {
+        setAspectRatioSettingsBuildTimeFlagEnabled(false);
+        assertFalse(UserAspectRatioManager.isFeatureEnabled(mContext));
+    }
+
+    @Test
+    public void testIsFeatureEnabled_disabledRuntimeFlag_returnFalse() {
+        setAspectRatioSettingsDeviceConfigEnabled("false" /* enabled */, false /* makeDefault */);
+        assertFalse(UserAspectRatioManager.isFeatureEnabled(mContext));
+    }
+
+    @Test
+    public void testIsFullscreenOptionEnabled() {
+        assertTrue(mUtils.isFullscreenOptionEnabled(mPackageName));
+    }
+
+    @Test
+    public void testIsFullscreenOptionEnabled_settingsDisabled_returnFalse() {
+        setAspectRatioFullscreenBuildTimeFlagEnabled(false);
+        assertFalse(mUtils.isFullscreenOptionEnabled(mPackageName));
+    }
+
+    @Test
+    public void testIsFullscreenOptionEnabled_disabledBuildTimeFlag_returnFalse() {
+        setAspectRatioFullscreenBuildTimeFlagEnabled(false);
+        assertFalse(mUtils.isFullscreenOptionEnabled(mPackageName));
+    }
+
+    @Test
+    public void testIsFullscreenOptionEnabled_disabledRuntimeFlag_returnFalse() {
+        setAspectRatioFullscreenDeviceConfigEnabled("false" /* enabled */, false /*makeDefault */);
+        assertFalse(mUtils.isFullscreenOptionEnabled(mPackageName));
+    }
+
+    @Test
+    public void testIsFullscreenOptionEnabled_propertyFalse_returnsFalse()
+            throws PackageManager.NameNotFoundException {
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, false);
+        assertFalse(mUtils.isFullscreenOptionEnabled(mPackageName));
+    }
+
+    @Test
+    public void testIsFullscreenOptionEnabled_propertyTrue_configDisabled_returnsFalse()
+            throws PackageManager.NameNotFoundException {
+        mockProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, true);
+        setAspectRatioFullscreenDeviceConfigEnabled("false" /* enabled */, false /*makeDefault */);
+
+        assertFalse(mUtils.isFullscreenOptionEnabled(mPackageName));
+    }
+
+    @Test
+    public void testHasAspectRatioOption_fullscreen() {
+        assertTrue(mUtils.hasAspectRatioOption(USER_MIN_ASPECT_RATIO_FULLSCREEN,
+                mPackageName));
+        assertTrue(mUtils.hasAspectRatioOption(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                mPackageName));
+
+        // Only fullscreen option should be disabled
+        when(mUtils.isFullscreenOptionEnabled(mPackageName)).thenReturn(false);
+        assertFalse(mUtils.hasAspectRatioOption(USER_MIN_ASPECT_RATIO_FULLSCREEN,
+                mPackageName));
+        assertTrue(mUtils.hasAspectRatioOption(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                mPackageName));
+    }
+
+    @Test
+    public void testGetUserMinAspectRatioEntry() {
+        // R.string.user_aspect_ratio_app_default
+        final String appDefault = ResourcesUtils.getResourcesString(mContext,
+                "user_aspect_ratio_app_default");
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_UNSET, mPackageName))
+                .isEqualTo(appDefault);
+        // should always return default if value does not correspond to anything
+        assertThat(mUtils.getUserMinAspectRatioEntry(-1, mPackageName))
+                .isEqualTo(appDefault);
+        // R.string.user_aspect_ratio_half_screen
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                mPackageName)).isEqualTo(ResourcesUtils.getResourcesString(mContext,
+                        "user_aspect_ratio_half_screen"));
+        // R.string.user_aspect_ratio_3_2
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_3_2, mPackageName))
+                .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_3_2"));
+        // R,string.user_aspect_ratio_4_3
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_4_3, mPackageName))
+                .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_4_3"));
+        // R.string.user_aspect_ratio_16_9
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_16_9, mPackageName))
+                .isEqualTo(ResourcesUtils.getResourcesString(mContext, "user_aspect_ratio_16_9"));
+        // R.string.user_aspect_ratio_fullscreen
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_FULLSCREEN,
+                mPackageName)).isEqualTo(ResourcesUtils.getResourcesString(mContext,
+                        "user_aspect_ratio_fullscreen"));
+    }
+
+    @Test
+    public void testGetUserMinAspectRatioEntry_fullscreenDisabled_shouldReturnDefault() {
+        setAspectRatioFullscreenBuildTimeFlagEnabled(false);
+        assertThat(mUtils.getUserMinAspectRatioEntry(USER_MIN_ASPECT_RATIO_FULLSCREEN,
+                mPackageName)).isEqualTo(ResourcesUtils.getResourcesString(mContext,
+                        "user_aspect_ratio_app_default"));
+    }
+
+    private void mockProperty(String propertyName, boolean value)
+            throws PackageManager.NameNotFoundException {
+        PackageManager.Property prop = new PackageManager.Property(
+                propertyName, value, mPackageName, "" /* className */);
+        PackageManager pm = mock(PackageManager.class);
+        when(mContext.getPackageManager()).thenReturn(pm);
+        when(pm.getProperty(propertyName, mPackageName)).thenReturn(prop);
+    }
+
+    private void setAspectRatioSettingsBuildTimeFlagEnabled(boolean enabled) {
+        when(mResources.getBoolean(R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
+                .thenReturn(enabled);
+    }
+
+    private void setAspectRatioSettingsDeviceConfigEnabled(String enabled, boolean makeDefault) {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS, enabled, makeDefault);
+    }
+
+    private void setAspectRatioFullscreenBuildTimeFlagEnabled(boolean enabled) {
+        when(mResources.getBoolean(R.bool.config_appCompatUserAppAspectRatioFullscreenIsEnabled))
+                .thenReturn(enabled);
+    }
+
+    private void setAspectRatioFullscreenDeviceConfigEnabled(String enabled, boolean makeDefault) {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN, enabled, makeDefault);
+    }
+
+    private void addResolveInfoLauncherEntry(String packageName) {
+        final ResolveInfo resolveInfo = mock(ResolveInfo.class);
+        final ActivityInfo activityInfo = mock(ActivityInfo.class);
+        activityInfo.packageName = packageName;
+        resolveInfo.activityInfo = activityInfo;
+        mUtils.addInfoHasLauncherEntry(resolveInfo);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/bluetooth/BlockingPrefWithSliceControllerTest.java b/tests/unit/src/com/android/settings/bluetooth/BlockingPrefWithSliceControllerTest.java
index 65b6977..d5a2585 100644
--- a/tests/unit/src/com/android/settings/bluetooth/BlockingPrefWithSliceControllerTest.java
+++ b/tests/unit/src/com/android/settings/bluetooth/BlockingPrefWithSliceControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.bluetooth;
 
+import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -24,8 +26,8 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.PendingIntent;
-import android.content.Context;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 
@@ -42,20 +44,20 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.settings.bluetooth.BlockingPrefWithSliceController;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+@RunWith(AndroidJUnit4.class)
 public class BlockingPrefWithSliceControllerTest {
     private static final String KEY = "bt_device_slice_category";
-    private static final String TEST_URI_AUTHORITY = "com.android.authority.test";
+    private static final String TEST_URI_AUTHORITY = "com.android.settings";
     private static final String TEST_EXTRA_INTENT = "EXTRA_INTENT";
     private static final String TEST_EXTRA_PENDING_INTENT = "EXTRA_PENDING_INTENT";
     private static final String TEST_INTENT_ACTION = "test";
@@ -71,6 +73,8 @@
     private LiveData<Slice> mLiveData;
     @Mock
     private PreferenceCategory mPreferenceCategory;
+    @Captor
+    ArgumentCaptor<Preference> mPreferenceArgumentCaptor;
 
     private Context mContext;
     private BlockingPrefWithSliceController mController;
@@ -130,6 +134,14 @@
         verify(mController.mPreferenceCategory).addPreference(any());
     }
 
+    @Test
+    public void onChanged_sliceWithoutValidIntent_makePreferenceUnselectable() {
+        mController.onChanged(buildTestSlice());
+
+        verify(mController.mPreferenceCategory).addPreference(mPreferenceArgumentCaptor.capture());
+        assertThat(mPreferenceArgumentCaptor.getValue().isSelectable()).isFalse();
+    }
+
     private Slice buildTestSlice() {
         Uri uri =
                 new Uri.Builder()
@@ -141,7 +153,7 @@
         IconCompat icon = mock(IconCompat.class);
         listBuilder.addRow(
                 new RowBuilder()
-                        .setTitleItem(icon, ListBuilder.ICON_IMAGE)
+                        .setTitleItem(icon, ICON_IMAGE)
                         .setTitle(TEST_SLICE_TITLE)
                         .setSubtitle(TEST_SLICE_SUBTITLE)
                         .setPrimaryAction(
@@ -153,7 +165,7 @@
                                                 PendingIntent.FLAG_UPDATE_CURRENT
                                                         | PendingIntent.FLAG_IMMUTABLE),
                                         icon,
-                                        ListBuilder.ICON_IMAGE,
+                                        ICON_IMAGE,
                                         "")));
         return listBuilder.build();
     }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
new file mode 100644
index 0000000..0509d8a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FakeFingerprintManagerInteractor.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fingerprint2.domain.interactor
+
+import android.hardware.biometrics.SensorProperties
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
+class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
+
+  var enrollableFingerprints: Int = 5
+  var enrolledFingerprintsInternal: MutableList<FingerprintViewModel> = mutableListOf()
+  var challengeToGenerate: Pair<Long, ByteArray> = Pair(-1L, byteArrayOf())
+  var authenticateAttempt = FingerprintAuthAttemptViewModel.Success(1)
+  var pressToAuthEnabled = true
+
+  var sensorProps =
+    listOf(
+      FingerprintSensorPropertiesInternal(
+        0 /* sensorId */,
+        SensorProperties.STRENGTH_STRONG,
+        5 /* maxEnrollmentsPerUser */,
+        emptyList() /* ComponentInfoInternal */,
+        TYPE_POWER_BUTTON,
+        true /* resetLockoutRequiresHardwareAuthToken */
+      )
+    )
+
+  override suspend fun authenticate(): FingerprintAuthAttemptViewModel {
+    return authenticateAttempt
+  }
+
+  override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> {
+    return challengeToGenerate
+  }
+  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
+    emit(enrolledFingerprintsInternal)
+  }
+
+  override fun canEnrollFingerprints(numFingerprints: Int): Flow<Boolean> = flow {
+    emit(numFingerprints < enrollableFingerprints)
+  }
+
+  override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
+
+  override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean {
+    return enrolledFingerprintsInternal.remove(fp)
+  }
+
+  override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {}
+
+  override suspend fun hasSideFps(): Boolean {
+    return sensorProps.any { it.isAnySidefpsType }
+  }
+
+  override suspend fun pressToAuthEnabled(): Boolean {
+    return pressToAuthEnabled
+  }
+
+  override suspend fun sensorPropertiesInternal(): List<FingerprintSensorPropertiesInternal> =
+    sensorProps
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
new file mode 100644
index 0000000..7af740a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fingerprint2.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.hardware.fingerprint.Fingerprint
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintManager.CryptoObject
+import android.hardware.fingerprint.FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+import android.os.CancellationSignal
+import android.os.Handler
+import androidx.test.core.app.ApplicationProvider
+import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.password.ChooseLockSettingsHelper
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.last
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.nullable
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class FingerprintManagerInteractorTest {
+
+  @JvmField @Rule var rule = MockitoJUnit.rule()
+  private lateinit var underTest: FingerprintManagerInteractor
+  private var context: Context = ApplicationProvider.getApplicationContext()
+  private var backgroundDispatcher = StandardTestDispatcher()
+  @Mock private lateinit var fingerprintManager: FingerprintManager
+  @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
+
+  private var testScope = TestScope(backgroundDispatcher)
+  private var pressToAuthProvider = { true }
+
+  @Before
+  fun setup() {
+    underTest =
+      FingerprintManagerInteractorImpl(
+        context,
+        backgroundDispatcher,
+        fingerprintManager,
+        gateKeeperPasswordProvider,
+        pressToAuthProvider,
+      )
+  }
+
+  @Test
+  fun testEmptyFingerprints() =
+    testScope.runTest {
+      Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
+        .thenReturn(emptyList())
+
+      val emptyFingerprintList: List<Fingerprint> = emptyList()
+      assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
+    }
+
+  @Test
+  fun testOneFingerprint() =
+    testScope.runTest {
+      val expected = Fingerprint("Finger 1,", 2, 3L)
+      val fingerprintList: List<Fingerprint> = listOf(expected)
+      Mockito.`when`(fingerprintManager.getEnrolledFingerprints(Mockito.anyInt()))
+        .thenReturn(fingerprintList)
+
+      val list = underTest.enrolledFingerprints.last()
+      assertThat(list.size).isEqualTo(fingerprintList.size)
+      val actual = list[0]
+      assertThat(actual.name).isEqualTo(expected.name)
+      assertThat(actual.fingerId).isEqualTo(expected.biometricId)
+      assertThat(actual.deviceId).isEqualTo(expected.deviceId)
+    }
+
+  @Test
+  fun testCanEnrollFingerprint() =
+    testScope.runTest {
+      val mockContext = Mockito.mock(Context::class.java)
+      val resources = Mockito.mock(Resources::class.java)
+      Mockito.`when`(mockContext.resources).thenReturn(resources)
+      Mockito.`when`(resources.getInteger(anyInt())).thenReturn(3)
+      underTest =
+        FingerprintManagerInteractorImpl(
+          mockContext,
+          backgroundDispatcher,
+          fingerprintManager,
+          gateKeeperPasswordProvider,
+          pressToAuthProvider,
+        )
+
+      assertThat(underTest.canEnrollFingerprints(2).last()).isTrue()
+      assertThat(underTest.canEnrollFingerprints(3).last()).isFalse()
+    }
+
+  @Test
+  fun testGenerateChallenge() =
+    testScope.runTest {
+      val byteArray = byteArrayOf(5, 3, 2)
+      val challenge = 100L
+      val intent = Intent()
+      intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, challenge)
+      Mockito.`when`(
+          gateKeeperPasswordProvider.requestGatekeeperHat(
+            any(Intent::class.java),
+            anyLong(),
+            anyInt()
+          )
+        )
+        .thenReturn(byteArray)
+
+      val generateChallengeCallback: ArgumentCaptor<FingerprintManager.GenerateChallengeCallback> =
+        ArgumentCaptor.forClass(FingerprintManager.GenerateChallengeCallback::class.java)
+
+      var result: Pair<Long, ByteArray?>? = null
+      val job = testScope.launch { result = underTest.generateChallenge(1L) }
+      runCurrent()
+
+      Mockito.verify(fingerprintManager)
+        .generateChallenge(anyInt(), capture(generateChallengeCallback))
+      generateChallengeCallback.value.onChallengeGenerated(1, 2, challenge)
+
+      runCurrent()
+      job.cancelAndJoin()
+
+      assertThat(result?.first).isEqualTo(challenge)
+      assertThat(result?.second).isEqualTo(byteArray)
+    }
+
+  @Test
+  fun testRemoveFingerprint_succeeds() =
+    testScope.runTest {
+      val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
+      val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
+
+      val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
+        ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
+
+      var result: Boolean? = null
+      val job =
+        testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
+      runCurrent()
+
+      Mockito.verify(fingerprintManager)
+        .remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
+      removalCallback.value.onRemovalSucceeded(fingerprintToRemove, 1)
+
+      runCurrent()
+      job.cancelAndJoin()
+
+      assertThat(result).isTrue()
+    }
+
+  @Test
+  fun testRemoveFingerprint_fails() =
+    testScope.runTest {
+      val fingerprintViewModelToRemove = FingerprintViewModel("Finger 2", 1, 2L)
+      val fingerprintToRemove = Fingerprint("Finger 2", 1, 2L)
+
+      val removalCallback: ArgumentCaptor<FingerprintManager.RemovalCallback> =
+        ArgumentCaptor.forClass(FingerprintManager.RemovalCallback::class.java)
+
+      var result: Boolean? = null
+      val job =
+        testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
+      runCurrent()
+
+      Mockito.verify(fingerprintManager)
+        .remove(any(Fingerprint::class.java), anyInt(), capture(removalCallback))
+      removalCallback.value.onRemovalError(
+        fingerprintToRemove,
+        100,
+        "Oh no, we couldn't find that one"
+      )
+
+      runCurrent()
+      job.cancelAndJoin()
+
+      assertThat(result).isFalse()
+    }
+
+  @Test
+  fun testRenameFingerprint_succeeds() =
+    testScope.runTest {
+      val fingerprintToRename = FingerprintViewModel("Finger 2", 1, 2L)
+
+      underTest.renameFingerprint(fingerprintToRename, "Woo")
+
+      Mockito.verify(fingerprintManager)
+        .rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
+    }
+
+  @Test
+  fun testAuth_succeeds() =
+    testScope.runTest {
+      val fingerprint = Fingerprint("Woooo", 100, 101L)
+
+      var result: FingerprintAuthAttemptViewModel? = null
+      val job = launch { result = underTest.authenticate() }
+
+      val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
+        ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
+
+      runCurrent()
+
+      Mockito.verify(fingerprintManager)
+        .authenticate(
+          nullable(CryptoObject::class.java),
+          any(CancellationSignal::class.java),
+          capture(authCallback),
+          nullable(Handler::class.java),
+          anyInt()
+        )
+      authCallback.value.onAuthenticationSucceeded(
+        FingerprintManager.AuthenticationResult(null, fingerprint, 1, false)
+      )
+
+      runCurrent()
+      job.cancelAndJoin()
+      assertThat(result).isEqualTo(FingerprintAuthAttemptViewModel.Success(fingerprint.biometricId))
+    }
+
+  @Test
+  fun testAuth_lockout() =
+    testScope.runTest {
+      var result: FingerprintAuthAttemptViewModel? = null
+      val job = launch { result = underTest.authenticate() }
+
+      val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> =
+        ArgumentCaptor.forClass(FingerprintManager.AuthenticationCallback::class.java)
+
+      runCurrent()
+
+      Mockito.verify(fingerprintManager)
+        .authenticate(
+          nullable(CryptoObject::class.java),
+          any(CancellationSignal::class.java),
+          capture(authCallback),
+          nullable(Handler::class.java),
+          anyInt()
+        )
+      authCallback.value.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
+
+      runCurrent()
+      job.cancelAndJoin()
+      assertThat(result)
+        .isEqualTo(
+          FingerprintAuthAttemptViewModel.Error(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "Lockout!!")
+        )
+    }
+
+  private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+  private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+  private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt
new file mode 100644
index 0000000..4e1f6b1
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsNavigationViewModelTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fingerprint2.viewmodel
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.EnrollFirstFingerprint
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettings
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FinishSettingsWithResult
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.LaunchConfirmDeviceCredential
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.NextStepViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.ShowSettings
+import com.android.settings.fingerprint2.domain.interactor.FakeFingerprintManagerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class FingerprintSettingsNavigationViewModelTest {
+
+  @JvmField @Rule var rule = MockitoJUnit.rule()
+
+  @get:Rule val instantTaskRule = InstantTaskExecutorRule()
+
+  private lateinit var underTest: FingerprintSettingsNavigationViewModel
+  private val defaultUserId = 0
+  private var backgroundDispatcher = StandardTestDispatcher()
+  private var testScope = TestScope(backgroundDispatcher)
+  private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
+
+  @Before
+  fun setup() {
+    fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
+    backgroundDispatcher = StandardTestDispatcher()
+    testScope = TestScope(backgroundDispatcher)
+    Dispatchers.setMain(backgroundDispatcher)
+
+    underTest =
+      FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
+          defaultUserId,
+          fakeFingerprintManagerInteractor,
+          backgroundDispatcher,
+          null,
+          null,
+        )
+        .create(FingerprintSettingsNavigationViewModel::class.java)
+  }
+
+  @After
+  fun tearDown() {
+    Dispatchers.resetMain()
+  }
+
+  @Test
+  fun testNoGateKeeper_launchesConfirmDeviceCredential() =
+    testScope.runTest {
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      runCurrent()
+      assertThat(nextStep).isEqualTo(LaunchConfirmDeviceCredential(defaultUserId))
+      job.cancel()
+    }
+
+  @Test
+  fun testConfirmDevice_fails() =
+    testScope.runTest {
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(false, null)
+      runCurrent()
+
+      assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+      job.cancel()
+    }
+
+  @Test
+  fun confirmDeviceSuccess_noGateKeeper() =
+    testScope.runTest {
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, null)
+      runCurrent()
+
+      assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+      job.cancel()
+    }
+
+  @Test
+  fun confirmDeviceSuccess_launchesEnrollment_ifNoPreviousEnrollments() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, 10L)
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(EnrollFirstFingerprint(defaultUserId, 10L, null, null))
+      job.cancel()
+    }
+
+  @Test
+  fun firstEnrollment_fails() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, 10L)
+      underTest.onEnrollFirstFailure("We failed!!")
+      runCurrent()
+
+      assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+      job.cancel()
+    }
+
+  @Test
+  fun firstEnrollment_failsWithReason() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      val failStr = "We failed!!"
+      val failReason = 101
+
+      underTest.onConfirmDevice(true, 10L)
+      underTest.onEnrollFirstFailure(failStr, failReason)
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(FinishSettingsWithResult(failReason, failStr))
+      job.cancel()
+    }
+
+  @Test
+  fun firstEnrollmentSucceeds_noToken() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, 10L)
+      underTest.onEnrollFirst(null, null)
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(FinishSettings("Error, empty token"))
+      job.cancel()
+    }
+
+  @Test
+  fun firstEnrollmentSucceeds_noKeyChallenge() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      val byteArray = ByteArray(1) { 3 }
+
+      underTest.onConfirmDevice(true, 10L)
+      underTest.onEnrollFirst(byteArray, null)
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(FinishSettings("Error, empty keyChallenge"))
+      job.cancel()
+    }
+
+  @Test
+  fun firstEnrollment_succeeds() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf()
+
+      var nextStep: NextStepViewModel? = null
+      val job = testScope.launch { underTest.nextStep.collect { nextStep = it } }
+
+      val byteArray = ByteArray(1) { 3 }
+      val keyChallenge = 89L
+
+      underTest.onConfirmDevice(true, 10L)
+      underTest.onEnrollFirst(byteArray, keyChallenge)
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(ShowSettings)
+      job.cancel()
+    }
+
+  @Test
+  fun enrollAdditionalFingerprints_fails() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+        mutableListOf(FingerprintViewModel("a", 1, 3L))
+      fakeFingerprintManagerInteractor.challengeToGenerate = Pair(4L, byteArrayOf(3, 3, 1))
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, 10L)
+      runCurrent()
+      underTest.onEnrollAdditionalFailure()
+      runCurrent()
+
+      assertThat(nextStep).isInstanceOf(FinishSettings::class.java)
+      job.cancel()
+    }
+
+  @Test
+  fun enrollAdditional_success() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+        mutableListOf(FingerprintViewModel("a", 1, 3L))
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, 10L)
+      underTest.onEnrollSuccess()
+
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(ShowSettings)
+      job.cancel()
+    }
+
+  @Test
+  fun confirmDeviceCredential_withEnrolledFingerprint_showsSettings() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+        mutableListOf(FingerprintViewModel("a", 1, 3L))
+      fakeFingerprintManagerInteractor.challengeToGenerate = Pair(10L, byteArrayOf(1, 2, 3))
+
+      var nextStep: NextStepViewModel? = null
+      val job = launch { underTest.nextStep.collect { nextStep = it } }
+
+      underTest.onConfirmDevice(true, 10L)
+      runCurrent()
+
+      assertThat(nextStep).isEqualTo(ShowSettings)
+      job.cancel()
+    }
+}
diff --git a/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
new file mode 100644
index 0000000..d430827
--- /dev/null
+++ b/tests/unit/src/com/android/settings/fingerprint2/viewmodel/FingerprintSettingsViewModelTest.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fingerprint2.viewmodel
+
+import android.hardware.biometrics.SensorProperties
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintAuthAttemptViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsNavigationViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintSettingsViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.FingerprintViewModel
+import com.android.settings.biometrics.fingerprint2.ui.viewmodel.PreferenceViewModel
+import com.android.settings.fingerprint2.domain.interactor.FakeFingerprintManagerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class FingerprintSettingsViewModelTest {
+
+  @JvmField @Rule var rule = MockitoJUnit.rule()
+
+  @get:Rule val instantTaskRule = InstantTaskExecutorRule()
+
+  private lateinit var underTest: FingerprintSettingsViewModel
+  private lateinit var navigationViewModel: FingerprintSettingsNavigationViewModel
+  private val defaultUserId = 0
+  private var backgroundDispatcher = StandardTestDispatcher()
+  private var testScope = TestScope(backgroundDispatcher)
+  private lateinit var fakeFingerprintManagerInteractor: FakeFingerprintManagerInteractor
+
+  @Before
+  fun setup() {
+    fakeFingerprintManagerInteractor = FakeFingerprintManagerInteractor()
+    backgroundDispatcher = StandardTestDispatcher()
+    testScope = TestScope(backgroundDispatcher)
+    Dispatchers.setMain(backgroundDispatcher)
+
+    navigationViewModel =
+      FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
+          defaultUserId,
+          fakeFingerprintManagerInteractor,
+          backgroundDispatcher,
+          null,
+          null,
+        )
+        .create(FingerprintSettingsNavigationViewModel::class.java)
+
+    underTest =
+      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+          defaultUserId,
+          fakeFingerprintManagerInteractor,
+          backgroundDispatcher,
+          navigationViewModel,
+        )
+        .create(FingerprintSettingsViewModel::class.java)
+  }
+
+  @After
+  fun tearDown() {
+    Dispatchers.resetMain()
+  }
+
+  @Test
+  fun authenticate_DoesNotRun_ifOptical() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.sensorProps =
+        listOf(
+          FingerprintSensorPropertiesInternal(
+            0 /* sensorId */,
+            SensorProperties.STRENGTH_STRONG,
+            5 /* maxEnrollmentsPerUser */,
+            emptyList() /* ComponentInfoInternal */,
+            FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+            true /* resetLockoutRequiresHardwareAuthToken */
+          )
+        )
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+        mutableListOf(FingerprintViewModel("a", 1, 3L))
+
+      underTest =
+        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+            defaultUserId,
+            fakeFingerprintManagerInteractor,
+            backgroundDispatcher,
+            navigationViewModel,
+          )
+          .create(FingerprintSettingsViewModel::class.java)
+
+      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
+
+      underTest.shouldAuthenticate(true)
+      // Ensure we are showing settings
+      navigationViewModel.onConfirmDevice(true, 10L)
+
+      runCurrent()
+      advanceTimeBy(400)
+
+      assertThat(authAttempt).isNull()
+      job.cancel()
+    }
+
+  @Test
+  fun authenticate_DoesNotRun_ifUltrasonic() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.sensorProps =
+        listOf(
+          FingerprintSensorPropertiesInternal(
+            0 /* sensorId */,
+            SensorProperties.STRENGTH_STRONG,
+            5 /* maxEnrollmentsPerUser */,
+            emptyList() /* ComponentInfoInternal */,
+            FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+            true /* resetLockoutRequiresHardwareAuthToken */
+          )
+        )
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+        mutableListOf(FingerprintViewModel("a", 1, 3L))
+
+      underTest =
+        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+            defaultUserId,
+            fakeFingerprintManagerInteractor,
+            backgroundDispatcher,
+            navigationViewModel,
+          )
+          .create(FingerprintSettingsViewModel::class.java)
+
+      var authAttempt: FingerprintAuthAttemptViewModel? = null
+      val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
+
+      underTest.shouldAuthenticate(true)
+      navigationViewModel.onConfirmDevice(true, 10L)
+      advanceTimeBy(400)
+      runCurrent()
+
+      assertThat(authAttempt).isNull()
+      job.cancel()
+    }
+
+  @Test
+  fun authenticate_DoesRun_ifNotUdfps() =
+    testScope.runTest {
+      fakeFingerprintManagerInteractor.sensorProps =
+        listOf(
+          FingerprintSensorPropertiesInternal(
+            0 /* sensorId */,
+            SensorProperties.STRENGTH_STRONG,
+            5 /* maxEnrollmentsPerUser */,
+            emptyList() /* ComponentInfoInternal */,
+            FingerprintSensorProperties.TYPE_POWER_BUTTON,
+            true /* resetLockoutRequiresHardwareAuthToken */
+          )
+        )
+      fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
+        mutableListOf(FingerprintViewModel("a", 1, 3L))
+      val success = FingerprintAuthAttemptViewModel.Success(1)
+      fakeFingerprintManagerInteractor.authenticateAttempt = success
+
+      underTest =
+        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+            defaultUserId,
+            fakeFingerprintManagerInteractor,
+            backgroundDispatcher,
+            navigationViewModel,
+          )
+          .create(FingerprintSettingsViewModel::class.java)
+
+      var authAttempt: FingerprintAuthAttemptViewModel? = null
+
+      val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
+      underTest.shouldAuthenticate(true)
+      navigationViewModel.onConfirmDevice(true, 10L)
+      advanceTimeBy(400)
+      runCurrent()
+
+      assertThat(authAttempt).isEqualTo(success)
+      job.cancel()
+    }
+
+  @Test
+  fun deleteDialog_showAndDismiss() = runTest {
+    val fingerprintToDelete = FingerprintViewModel("A", 1, 10L)
+    fakeFingerprintManagerInteractor.enrolledFingerprintsInternal = mutableListOf(fingerprintToDelete)
+
+    underTest =
+      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+          defaultUserId,
+          fakeFingerprintManagerInteractor,
+          backgroundDispatcher,
+          navigationViewModel,
+        )
+        .create(FingerprintSettingsViewModel::class.java)
+
+    var dialog: PreferenceViewModel? = null
+    val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
+
+    // Move to the ShowSettings state
+    navigationViewModel.onConfirmDevice(true, 10L)
+    runCurrent()
+    underTest.onDeleteClicked(fingerprintToDelete)
+    runCurrent()
+
+    assertThat(dialog is PreferenceViewModel.DeleteDialog)
+    assertThat(dialog).isEqualTo(PreferenceViewModel.DeleteDialog(fingerprintToDelete))
+
+    underTest.deleteFingerprint(fingerprintToDelete)
+    underTest.onDeleteDialogFinished()
+    runCurrent()
+
+    assertThat(dialog).isNull()
+
+    dialogJob.cancel()
+  }
+}
diff --git a/tests/unit/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProviderImplTest.java b/tests/unit/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProviderImplTest.java
new file mode 100644
index 0000000..6675d5a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/inputmethod/KeyboardSettingsFeatureProviderImplTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Looper;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+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 KeyboardSettingsFeatureProviderImplTest {
+
+    private Context mContext;
+    private KeyboardSettingsFeatureProviderImpl mFeatureProvider;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mFeatureProvider = new KeyboardSettingsFeatureProviderImpl();
+    }
+
+    @Test
+    public void supportsFirmwareUpdate_defaultValue_returnsFalse() {
+        assertThat(mFeatureProvider.supportsFirmwareUpdate()).isFalse();
+    }
+
+    @Test
+    public void addFirmwareUpdateCategory_defaultValue_returnsFalse() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext);
+
+        assertThat(mFeatureProvider.addFirmwareUpdateCategory(mContext, screen)).isFalse();
+    }
+
+    @Test
+    public void getActionKeyIcon_defaultValue_returnsNull() {
+        assertThat(mFeatureProvider.getActionKeyIcon(mContext)).isNull();
+    }
+}
diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
index 63dca7e..f063042 100644
--- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
@@ -16,26 +16,32 @@
 
 package com.android.settings.network;
 
+import static com.android.settings.network.SubscriptionUtil.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME;
+import static com.android.settings.network.SubscriptionUtil.SUB_ID;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
-import com.android.settings.R;
-
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.settings.R;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -445,6 +451,35 @@
     }
 
     @Test
+    public void getUniqueDisplayName_hasRecord_useRecordBeTheResult() {
+        final SubscriptionInfo info1 = mock(SubscriptionInfo.class);
+        final SubscriptionInfo info2 = mock(SubscriptionInfo.class);
+        when(info1.getSubscriptionId()).thenReturn(SUBID_1);
+        when(info2.getSubscriptionId()).thenReturn(SUBID_2);
+        when(info1.getDisplayName()).thenReturn(CARRIER_1);
+        when(info2.getDisplayName()).thenReturn(CARRIER_1);
+        when(mSubMgr.getAvailableSubscriptionInfoList()).thenReturn(
+                Arrays.asList(info1, info2));
+
+        SharedPreferences sp = mock(SharedPreferences.class);
+        when(mContext.getSharedPreferences(
+                KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME, Context.MODE_PRIVATE)).thenReturn(sp);
+        when(sp.getString(eq(SUB_ID + SUBID_1), anyString())).thenReturn(CARRIER_1 + "6789");
+        when(sp.getString(eq(SUB_ID + SUBID_2), anyString())).thenReturn(CARRIER_1 + "4321");
+
+
+        final CharSequence nameOfSub1 =
+                SubscriptionUtil.getUniqueSubscriptionDisplayName(info1, mContext);
+        final CharSequence nameOfSub2 =
+                SubscriptionUtil.getUniqueSubscriptionDisplayName(info2, mContext);
+
+        assertThat(nameOfSub1).isNotNull();
+        assertThat(nameOfSub2).isNotNull();
+        assertEquals(CARRIER_1 + "6789", nameOfSub1.toString());
+        assertEquals(CARRIER_1 + "4321", nameOfSub2.toString());
+    }
+
+    @Test
     public void isInactiveInsertedPSim_nullSubInfo_doesNotCrash() {
         assertThat(SubscriptionUtil.isInactiveInsertedPSim(null)).isFalse();
     }
diff --git a/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java b/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java
new file mode 100644
index 0000000..88e3150
--- /dev/null
+++ b/tests/unit/src/com/android/settings/password/SaveAndFinishWorkerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.password;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SaveAndFinishWorkerTest {
+    @Test
+    public void testSetRequestWriteRepairModePassword_setLockCredentialFail() {
+        int userId = 0;
+        int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+        var chosenCredential = LockscreenCredential.createPassword("1234");
+        var currentCredential = LockscreenCredential.createNone();
+        var worker = new SaveAndFinishWorker();
+        var lpu = mock(LockPatternUtils.class);
+
+        when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(false);
+
+        worker.setRequestWriteRepairModePassword(true);
+        worker.prepare(lpu, chosenCredential, currentCredential, userId);
+        var result = worker.saveAndVerifyInBackground();
+
+        verify(lpu).setLockCredential(chosenCredential, currentCredential, userId);
+        verify(lpu, never()).verifyCredential(chosenCredential, userId, flags);
+        assertThat(result.first).isFalse();
+    }
+
+    @Test
+    public void testSetRequestWriteRepairModePassword_verifyCredentialFail() {
+        int userId = 0;
+        int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+        var chosenCredential = LockscreenCredential.createPassword("1234");
+        var currentCredential = LockscreenCredential.createNone();
+        var worker = new SaveAndFinishWorker();
+        var lpu = mock(LockPatternUtils.class);
+        var response = VerifyCredentialResponse.fromError();
+
+        when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true);
+        when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response);
+
+        worker.setRequestWriteRepairModePassword(true);
+        worker.prepare(lpu, chosenCredential, currentCredential, userId);
+        var result = worker.saveAndVerifyInBackground();
+
+        verify(lpu).setLockCredential(chosenCredential, currentCredential, userId);
+        verify(lpu).verifyCredential(chosenCredential, userId, flags);
+        assertThat(result.first).isTrue();
+        assertThat(result.second.getBooleanExtra(
+                ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, true))
+                .isFalse();
+    }
+
+    @Test
+    public void testSetRequestWriteRepairModePassword_verifyCredentialSucceed() {
+        int userId = 0;
+        int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+        var chosenCredential = LockscreenCredential.createPassword("1234");
+        var currentCredential = LockscreenCredential.createNone();
+        var worker = new SaveAndFinishWorker();
+        var lpu = mock(LockPatternUtils.class);
+        var response = new VerifyCredentialResponse.Builder().build();
+
+        when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true);
+        when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response);
+
+        worker.setRequestWriteRepairModePassword(true);
+        worker.prepare(lpu, chosenCredential, currentCredential, userId);
+        var result = worker.saveAndVerifyInBackground();
+
+        verify(lpu).setLockCredential(chosenCredential, currentCredential, userId);
+        verify(lpu).verifyCredential(chosenCredential, userId, flags);
+        assertThat(result.first).isTrue();
+        assertThat(result.second.getBooleanExtra(
+                ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, false))
+                .isTrue();
+    }
+
+    @Test
+    public void testSetRequestWriteRepairModePassword_verifyCredentialSucceed_noGkPwHandle() {
+        int userId = 0;
+        int flags = LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW
+                | LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
+        var chosenCredential = LockscreenCredential.createPassword("1234");
+        var currentCredential = LockscreenCredential.createNone();
+        var worker = new SaveAndFinishWorker();
+        var lpu = mock(LockPatternUtils.class);
+        var response = new VerifyCredentialResponse.Builder().build();
+
+        when(lpu.setLockCredential(chosenCredential, currentCredential, userId)).thenReturn(true);
+        when(lpu.verifyCredential(chosenCredential, userId, flags)).thenReturn(response);
+
+        worker.setRequestWriteRepairModePassword(true);
+        worker.setRequestGatekeeperPasswordHandle(true);
+        worker.prepare(lpu, chosenCredential, currentCredential, userId);
+        var result = worker.saveAndVerifyInBackground();
+
+        verify(lpu).setLockCredential(chosenCredential, currentCredential, userId);
+        verify(lpu).verifyCredential(chosenCredential, userId, flags);
+        assertThat(result.first).isTrue();
+        assertThat(result.second.getBooleanExtra(
+                ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL, false))
+                .isTrue();
+        assertThat(result.second.getLongExtra(
+                ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, -1))
+                .isEqualTo(-1);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index 697217b..49ce2cc 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -27,6 +27,7 @@
 import com.android.settings.biometrics.face.FaceFeatureProvider;
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
 import com.android.settings.bluetooth.BluetoothFeatureProvider;
+import com.android.settings.connecteddevice.stylus.StylusFeatureProvider;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
 import com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFeatureProvider;
@@ -37,6 +38,7 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.gestures.AssistGestureFeatureProvider;
 import com.android.settings.homepage.contextualcards.ContextualCardFeatureProvider;
+import com.android.settings.inputmethod.KeyboardSettingsFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.overlay.DockUpdaterFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
@@ -90,6 +92,8 @@
     public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
     public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
     public WifiFeatureProvider mWifiFeatureProvider;
+    public KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
+    public StylusFeatureProvider mStylusFeatureProvider;
 
     /**
      * Call this in {@code @Before} method of the test class to use fake factory.
@@ -133,6 +137,8 @@
         mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class);
         mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class);
         mWifiFeatureProvider = mock(WifiFeatureProvider.class);
+        mKeyboardSettingsFeatureProvider = mock(KeyboardSettingsFeatureProvider.class);
+        mStylusFeatureProvider = mock(StylusFeatureProvider.class);
     }
 
     @Override
@@ -156,7 +162,7 @@
     }
 
     @Override
-    public BatterySettingsFeatureProvider getBatterySettingsFeatureProvider(Context context) {
+    public BatterySettingsFeatureProvider getBatterySettingsFeatureProvider() {
         return batterySettingsFeatureProvider;
     }
 
@@ -289,4 +295,14 @@
     public WifiFeatureProvider getWifiFeatureProvider() {
         return mWifiFeatureProvider;
     }
+
+    @Override
+    public KeyboardSettingsFeatureProvider getKeyboardSettingsFeatureProvider() {
+        return mKeyboardSettingsFeatureProvider;
+    }
+
+    @Override
+    public StylusFeatureProvider getStylusFeatureProvider() {
+        return mStylusFeatureProvider;
+    }
 }
diff --git a/tests/unit/src/com/android/settings/wifi/dpp/WifiQrCodeTest.java b/tests/unit/src/com/android/settings/wifi/dpp/WifiQrCodeTest.java
new file mode 100644
index 0000000..e3a8ca5
--- /dev/null
+++ b/tests/unit/src/com/android/settings/wifi/dpp/WifiQrCodeTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.dpp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class WifiQrCodeTest {
+    @Test
+    public void testZxParsing_validCode() {
+        WifiNetworkConfig config = new WifiQrCode("WIFI:S:testAbC;T:nopass").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("testAbC");
+        assertThat(config.getSecurity()).isEqualTo("nopass");
+
+        config = new WifiQrCode(
+                "WIFI:S:reallyLONGone;T:WEP;P:somepasswo#%^**123rd").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("reallyLONGone");
+        assertThat(config.getSecurity()).isEqualTo("WEP");
+        assertThat(config.getPreSharedKey()).isEqualTo("somepasswo#%^**123rd");
+
+        config = new WifiQrCode("WIFI:S:anotherone;T:WPA;P:3#=3j9asicla").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("anotherone");
+        assertThat(config.getSecurity()).isEqualTo("WPA");
+        assertThat(config.getPreSharedKey()).isEqualTo("3#=3j9asicla");
+
+        config = new WifiQrCode("WIFI:S:xx;T:SAE;P:a").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("xx");
+        assertThat(config.getSecurity()).isEqualTo("SAE");
+        assertThat(config.getPreSharedKey()).isEqualTo("a");
+    }
+
+    @Test
+    public void testZxParsing_invalidCodeButShouldWork() {
+        WifiNetworkConfig config = new WifiQrCode(
+                "WIFI:S:testAbC; T:nopass").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("testAbC");
+        assertThat(config.getSecurity()).isEqualTo("nopass");
+
+        config = new WifiQrCode(
+                "WIFI:S:reallyLONGone;T:WEP; P:somepassword").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("reallyLONGone");
+        assertThat(config.getSecurity()).isEqualTo("WEP");
+        assertThat(config.getPreSharedKey()).isEqualTo("somepassword");
+
+        config = new WifiQrCode("WIFI: S:anotherone;T:WPA;P:abcdefghihklmn").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("anotherone");
+        assertThat(config.getSecurity()).isEqualTo("WPA");
+        assertThat(config.getPreSharedKey()).isEqualTo("abcdefghihklmn");
+
+        config = new WifiQrCode("WIFI: S:xx; T:SAE;   P:a").getWifiNetworkConfig();
+        assertThat(config.getSsid()).isEqualTo("xx");
+        assertThat(config.getSecurity()).isEqualTo("SAE");
+        assertThat(config.getPreSharedKey()).isEqualTo("a");
+    }
+}
diff --git a/tests/unit/src/com/android/settings/wifi/repository/SharedConnectivityRepositoryTest.java b/tests/unit/src/com/android/settings/wifi/repository/SharedConnectivityRepositoryTest.java
new file mode 100644
index 0000000..4aef552
--- /dev/null
+++ b/tests/unit/src/com/android/settings/wifi/repository/SharedConnectivityRepositoryTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.repository;
+
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class SharedConnectivityRepositoryTest {
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Spy
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock
+    private SharedConnectivityManager mManager;
+
+    private SharedConnectivityRepository mRepository;
+    private PendingIntent mIntent = PendingIntent
+            .getActivity(mContext, 0, new Intent("test"), FLAG_IMMUTABLE);
+    private SharedConnectivitySettingsState mState = new SharedConnectivitySettingsState.Builder()
+            .setInstantTetherSettingsPendingIntent(mIntent).build();
+
+    @Before
+    public void setUp() {
+        when(mContext.getSystemService(SharedConnectivityManager.class)).thenReturn(mManager);
+        when(mManager.getSettingsState()).thenReturn(mState);
+
+        mRepository = spy(new SharedConnectivityRepository(mContext, true /* isConfigEnabled */));
+    }
+
+    @Test
+    public void constructor_configEnabled_registerCallback() {
+        verify(mManager).registerCallback(any(), any());
+    }
+
+    @Test
+    public void constructor_configNotEnabled_doNotRegisterCallback() {
+        SharedConnectivityManager manager = mock(SharedConnectivityManager.class);
+        when(mContext.getSystemService(SharedConnectivityManager.class)).thenReturn(manager);
+
+        mRepository = new SharedConnectivityRepository(mContext, false /* isConfigEnabled */);
+
+        verify(manager, never()).registerCallback(any(), any());
+    }
+
+    @Test
+    public void isServiceAvailable_configEnabled_returnTrue() {
+        mRepository = new SharedConnectivityRepository(mContext, true /* isConfigEnabled */);
+
+        assertThat(mRepository.isServiceAvailable()).isTrue();
+    }
+
+    @Test
+    public void isServiceAvailable_configNotEnabled_returnFalse() {
+        mRepository = new SharedConnectivityRepository(mContext, false /* isConfigEnabled */);
+
+        assertThat(mRepository.isServiceAvailable()).isFalse();
+    }
+
+    @Test
+    public void getSettingsState_isNotNull() {
+        assertThat(mRepository.getSettingsState()).isNotNull();
+    }
+
+    @Test
+    public void handleLaunchSettings_managerNull_doNothing() {
+        when(mContext.getSystemService(SharedConnectivityManager.class)).thenReturn(null);
+        mRepository = spy(new SharedConnectivityRepository(mContext, true /* isConfigEnabled */));
+
+        mRepository.handleLaunchSettings();
+
+        verify(mRepository, never()).sendSettingsIntent(mIntent);
+    }
+
+    @Test
+    public void handleLaunchSettings_stageNull_doNothing() {
+        when(mManager.getSettingsState()).thenReturn(null);
+
+        mRepository.handleLaunchSettings();
+
+        verify(mRepository, never()).sendSettingsIntent(mIntent);
+    }
+
+    @Test
+    public void handleLaunchSettings_intentNull_doNothing() {
+        mState = new SharedConnectivitySettingsState.Builder()
+                .setInstantTetherSettingsPendingIntent(null).build();
+        when(mManager.getSettingsState()).thenReturn(mState);
+
+        mRepository.handleLaunchSettings();
+
+        verify(mRepository, never()).sendSettingsIntent(mIntent);
+    }
+
+    @Test
+    public void handleLaunchSettings_allReady_sendSettingsIntent() {
+        mRepository.handleLaunchSettings();
+
+        verify(mRepository).sendSettingsIntent(mIntent);
+    }
+}
diff --git a/tests/unit/src/com/android/settings/wifi/tether/WifiTetherViewModelTest.java b/tests/unit/src/com/android/settings/wifi/tether/WifiTetherViewModelTest.java
index af1f62b..1c1473f 100644
--- a/tests/unit/src/com/android/settings/wifi/tether/WifiTetherViewModelTest.java
+++ b/tests/unit/src/com/android/settings/wifi/tether/WifiTetherViewModelTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.wifi.tether;
 
+import static com.android.settings.wifi.tether.WifiTetherViewModel.RES_INSTANT_HOTSPOT_SUMMARY_OFF;
+import static com.android.settings.wifi.tether.WifiTetherViewModel.RES_INSTANT_HOTSPOT_SUMMARY_ON;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -23,12 +26,15 @@
 
 import android.app.Application;
 import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
 
 import androidx.lifecycle.MutableLiveData;
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.wifi.repository.SharedConnectivityRepository;
 import com.android.settings.wifi.repository.WifiHotspotRepository;
 
 import org.junit.Before;
@@ -36,6 +42,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -45,8 +52,8 @@
 public class WifiTetherViewModelTest {
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Mock
-    Application mApplication;
+    @Spy
+    Application mApplication = ApplicationProvider.getApplicationContext();
     @Mock
     Executor mExecutor;
     @Mock
@@ -57,6 +64,12 @@
     MutableLiveData<Integer> mSpeedType;
     @Mock
     private MutableLiveData<Boolean> mRestarting;
+    @Mock
+    private SharedConnectivityRepository mSharedConnectivityRepository;
+    @Mock
+    private MutableLiveData<SharedConnectivitySettingsState> mSettingsState;
+    @Mock
+    private MutableLiveData<String> mInstantHotspotSummary;
 
     WifiTetherViewModel mViewModel;
 
@@ -70,8 +83,18 @@
         when(mWifiHotspotRepository.getSecurityType()).thenReturn(mSecurityType);
         when(mWifiHotspotRepository.getSpeedType()).thenReturn(mSpeedType);
         when(mWifiHotspotRepository.getRestarting()).thenReturn(mRestarting);
+        when(featureFactory.getWifiFeatureProvider().getSharedConnectivityRepository())
+                .thenReturn(mSharedConnectivityRepository);
+        when(mSharedConnectivityRepository.isServiceAvailable()).thenReturn(true);
+        when(mSharedConnectivityRepository.getSettingsState()).thenReturn(mSettingsState);
 
         mViewModel = new WifiTetherViewModel(mApplication);
+        mViewModel.mInstantHotspotSummary = mInstantHotspotSummary;
+    }
+
+    @Test
+    public void constructor_observeData() {
+        verify(mSettingsState).observeForever(mViewModel.mInstantHotspotStateObserver);
     }
 
     @Test
@@ -83,6 +106,7 @@
 
         verify(mSecurityType).removeObserver(mViewModel.mSecurityTypeObserver);
         verify(mSpeedType).removeObserver(mViewModel.mSpeedTypeObserver);
+        verify(mSettingsState).removeObserver(mViewModel.mInstantHotspotStateObserver);
     }
 
     @Test
@@ -141,4 +165,59 @@
     public void getRestarting_shouldNotReturnNull() {
         assertThat(mViewModel.getRestarting()).isNotNull();
     }
+
+    @Test
+    public void isInstantHotspotFeatureAvailable_serviceAvailable_returnTrue() {
+        when(mSharedConnectivityRepository.isServiceAvailable()).thenReturn(true);
+
+        assertThat(mViewModel.isInstantHotspotFeatureAvailable()).isTrue();
+    }
+
+    @Test
+    public void isInstantHotspotFeatureAvailable_serviceNotAvailable_returnFalse() {
+        when(mSharedConnectivityRepository.isServiceAvailable()).thenReturn(false);
+
+        assertThat(mViewModel.isInstantHotspotFeatureAvailable()).isFalse();
+    }
+
+    @Test
+    public void getInstantHotspotSummary_isNotNull() {
+        assertThat(mViewModel.getInstantHotspotSummary()).isNotNull();
+    }
+
+    @Test
+    public void onInstantHotspotStateChanged_stageNull_summarySetValueNull() {
+        mViewModel.onInstantHotspotStateChanged(null);
+
+        verify(mInstantHotspotSummary).setValue(null);
+    }
+
+    @Test
+    public void onInstantHotspotStateChanged_stateEnabled_summarySetValueOn() {
+        SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder()
+                .setInstantTetherEnabled(true).build();
+
+        mViewModel.onInstantHotspotStateChanged(state);
+
+        verify(mInstantHotspotSummary)
+                .setValue(mApplication.getString(RES_INSTANT_HOTSPOT_SUMMARY_ON));
+    }
+
+    @Test
+    public void onInstantHotspotStateChanged_stateNotEnabled_recordVisibleSummaryOff() {
+        SharedConnectivitySettingsState state = new SharedConnectivitySettingsState.Builder()
+                .setInstantTetherEnabled(false).build();
+
+        mViewModel.onInstantHotspotStateChanged(state);
+
+        verify(mInstantHotspotSummary)
+                .setValue(mApplication.getString(RES_INSTANT_HOTSPOT_SUMMARY_OFF));
+    }
+
+    @Test
+    public void launchInstantHotspotSettings_launchSettingsByRepository() {
+        mViewModel.launchInstantHotspotSettings();
+
+        verify(mSharedConnectivityRepository).launchSettings();
+    }
 }