Merge "Fix potential OOM caused by DataProcessManager" into main
diff --git a/aconfig/settings_datetime_flag_declarations.aconfig b/aconfig/settings_datetime_flag_declarations.aconfig
index 93d41d6..9951243 100644
--- a/aconfig/settings_datetime_flag_declarations.aconfig
+++ b/aconfig/settings_datetime_flag_declarations.aconfig
@@ -8,12 +8,3 @@
     description: "Enable the time feedback feature, a button to launch feedback in Date & Time Settings"
     bug: "283239837"
 }
-
-flag {
-    name: "revamp_toggles"
-    # "location" is used by the Android System Time team for feature flags.
-    namespace: "location"
-    description: "Makes the use location toggle dependent on automatic time zone detection"
-    bug: "296835792"
-}
-
diff --git a/res/layout/advanced_bt_entity_header.xml b/res/layout/advanced_bt_entity_header.xml
index 37ae843..83657b1 100644
--- a/res/layout/advanced_bt_entity_header.xml
+++ b/res/layout/advanced_bt_entity_header.xml
@@ -76,6 +76,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="16dp"
+        android:layoutDirection="ltr"
         android:orientation="horizontal">
 
         <include
diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml
index 252ab47..77bc4f0 100644
--- a/res/layout/advanced_bt_entity_sub.xml
+++ b/res/layout/advanced_bt_entity_sub.xml
@@ -21,6 +21,7 @@
     android:layout_width="0dp"
     android:layout_height="wrap_content"
     android:layout_weight="1"
+    android:layoutDirection="locale"
     android:focusable="true"
     android:orientation="vertical">
 
diff --git a/res/layout/apn_preference_layout.xml b/res/layout/apn_preference_layout.xml
deleted file mode 100644
index 9b6efe7..0000000
--- a/res/layout/apn_preference_layout.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:minHeight="?android:attr/listPreferredItemHeight"
-    android:focusable="false"
-    android:gravity="center_vertical">
-
-    <RelativeLayout
-        android:id="@+id/text_layout"
-        android:layout_width="0dip"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:layout_weight="1"
-        android:focusable="true"
-        android:clickable="true"
-        android:background="?android:attr/selectableItemBackground">
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:focusable="false"
-            android:labelFor="@id/apn_radio_button_frame"
-            android:singleLine="true"
-            android:textAppearance="?android:attr/textAppearanceListItem" />
-
-        <TextView
-            android:id="@android:id/summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_below="@android:id/title"
-            android:layout_alignStart="@android:id/title"
-            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
-            android:textColor="?android:attr/textColorSecondary"
-            android:focusable="false"
-            android:hyphenationFrequency="normalFast"
-            android:lineBreakWordStyle="phrase"
-            android:maxLines="2" />
-
-    </RelativeLayout>
-
-    <FrameLayout
-        android:id="@+id/apn_radio_button_frame"
-        android:layout_width="@dimen/min_tap_target_size"
-        android:layout_height="@dimen/min_tap_target_size"
-        android:layout_margin="8dp">
-
-        <RadioButton
-            android:id="@+id/apn_radiobutton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:clickable="false"
-            android:focusable="false" />
-    </FrameLayout>
-
-</LinearLayout>
diff --git a/res/layout/display_topology_preference.xml b/res/layout/display_topology_preference.xml
index d2e4300..beaf816 100644
--- a/res/layout/display_topology_preference.xml
+++ b/res/layout/display_topology_preference.xml
@@ -16,8 +16,9 @@
 
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/display_topology_pane_holder"
     android:importantForAccessibility="no"
-    android:layout_height="160dp"
+    android:layout_height="wrap_content"
     android:layout_width="match_parent"
     android:paddingHorizontal="@dimen/display_topology_pane_margin"
     android:orientation="horizontal">
@@ -27,14 +28,14 @@
         android:layout_width="match_parent"
         android:src="@drawable/display_topology_background"/>
     <FrameLayout
-        android:id="@+id/display_topology_container"
+        android:id="@+id/display_topology_pane_content"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
+        android:layout_height="match_parent"/>
     <TextView
         android:id="@+id/topology_hint"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:layout_gravity="top|center_horizontal"
-        android:paddingTop="10dp"
-        android:text="@string/external_display_topology_hint"/>
+        android:paddingBottom="10dp"
+        android:paddingTop="10dp" />
 </FrameLayout>
diff --git a/res/layout/preference_ambient_volume.xml b/res/layout/preference_ambient_volume.xml
new file mode 100644
index 0000000..a8595c6
--- /dev/null
+++ b/res/layout/preference_ambient_volume.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:clickable="false"
+    android:orientation="horizontal">
+
+    <include
+        layout="@layout/settingslib_icon_frame"
+        android:layout_width="48dp"
+        android:layout_height="48dp"/>
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:textColor="?android:attr/textColorPrimary"
+        android:ellipsize="marquee"
+        android:fadingEdge="horizontal"/>
+    <ImageView
+        android:id="@+id/expand_icon"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:padding="10dp"
+        android:contentDescription="@null"
+        android:tint="@androidprv:color/materialColorOnPrimaryContainer"
+        android:src="@drawable/ic_keyboard_arrow_down"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 200253a..87c3ce9 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -50,8 +50,6 @@
         <attr name="android:textAppearance" />
     </declare-styleable>
 
-    <attr name="apnPreferenceStyle" format="reference" />
-
     <attr name="slicePreferenceStyle" format="reference" />
 
     <attr name="cardPreferenceStyle" format="reference" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8faf62a..5a408d2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -164,6 +164,22 @@
     <string name="bluetooth_hearing_aids_presets_empty_list_message">There are no presets programmed by your audiologist</string>
     <!-- Message when selecting hearing aids presets failed. [CHAR LIMIT=NONE] -->
     <string name="bluetooth_hearing_aids_presets_error">Couldn\u2019t update preset</string>
+    <!-- Connected devices settings. Title for ambient volume control which controls the remote device's microphone input volume. [CHAR LIMIT=60] -->
+    <string name="bluetooth_ambient_volume_control">Surroundings</string>
+    <!-- Connected devices settings. Content description for the icon to expand the unified ambient volume control to left and right separated controls. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_ambient_volume_control_expand">Expand to left and right separated controls</string>
+    <!-- Connected devices settings. Content description for the icon to collapse the left and right separated ambient volume controls to unified control. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_ambient_volume_control_collapse">Collapse to unified control</string>
+    <!-- Connected devices settings. The text to show the control is for left side device. [CHAR LIMIT=30] -->
+    <string name="bluetooth_ambient_volume_control_left">Left</string>
+    <!-- Connected devices settings. The text to show the control is for right side device. [CHAR LIMIT=30] -->
+    <string name="bluetooth_ambient_volume_control_right">Right</string>
+    <!-- Connected devices settings. Content description for a button, that mute ambient volume [CHAR_LIMIT=NONE] -->
+    <string name="bluetooth_ambient_volume_mute">Mute surroundings</string>
+    <!-- Connected devices settings. Content description for a button, that unmute ambient volume [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_ambient_volume_unmute">Unmute surroundings</string>
+    <!-- Message when changing ambient state failed. [CHAR LIMIT=NONE] -->
+    <string name="bluetooth_ambient_volume_error">Couldn\u2019t update surroundings</string>
     <!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
     <string name="bluetooth_audio_routing_title">Audio output</string>
     <!-- Title for bluetooth audio routing page footer. [CHAR LIMIT=30] -->
@@ -4514,6 +4530,8 @@
     <string name="language_settings">Languages&#160;&amp; input</string>
     <!-- Title of setting on main settings screen.  This item will take the user to the screen to tweak settings related to languages -->
     <string name="languages_settings">Languages</string>
+    <!-- Title of setting on main settings screen.  This item will take the user to the screen to tweak settings related to language and region-->
+    <string name="language_and_region_settings">Language &amp; region</string>
     <!-- Title of setting on main settings screen.  This item will take the user to the screen to tweak settings related to keyboards -->
     <string name="keyboard_settings">Keyboard</string>
     <!-- Text displayed when user has restriction DISALLOW_CONFIG_LOCALE [CHAR LIMIT=NONE]-->
diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml
index ec0a747..c7638d3 100644
--- a/res/values/styles_preference.xml
+++ b/res/values/styles_preference.xml
@@ -20,7 +20,6 @@
 <resources>
 
     <style name="SettingsPreferenceTheme" parent="@style/PreferenceTheme.SettingsLib">
-        <item name="apnPreferenceStyle">@style/ApnPreference</item>
         <item name="cardPreferenceStyle">@style/CardPreference</item>
         <item name="slicePreferenceStyle">@style/SlicePreference</item>
         <item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>
@@ -32,10 +31,6 @@
         <item name="preferenceFragmentCompatStyle">@style/SetupWizardPreferenceFragmentStyle</item>
     </style>
 
-    <style name="ApnPreference" parent="@style/Preference.Material">
-        <item name="android:layout">@layout/apn_preference_layout</item>
-    </style>
-
     <style name="CardPreference" parent="@style/Preference.Material">
         <item name="android:layout">@layout/card_preference_layout</item>
     </style>
diff --git a/res/values/styles_preference_expressive.xml b/res/values/styles_preference_expressive.xml
index a6fe2f1..278fe00 100644
--- a/res/values/styles_preference_expressive.xml
+++ b/res/values/styles_preference_expressive.xml
@@ -20,7 +20,6 @@
 <resources>
 
     <style name="SettingsPreferenceTheme.Expressive" parent="@style/PreferenceTheme.SettingsLib.Expressive">
-        <item name="apnPreferenceStyle">@style/ApnPreference</item>
         <item name="cardPreferenceStyle">@style/CardPreference</item>
         <item name="slicePreferenceStyle">@style/SlicePreference</item>
         <item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index 88f60ef..3135f59 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -20,11 +20,15 @@
     android:key="app_data_usage_screen"
     android:title="@string/data_usage_app_summary_title">
 
+    <com.android.settingslib.widget.IntroPreference
+        android:key="app_header"
+        android:order="-10000"/>
+
     <com.android.settings.datausage.SpinnerPreference
         android:key="cycle"
         settings:controller="com.android.settings.datausage.AppDataUsageCycleController" />
 
-    <com.android.settings.spa.preference.ComposePreference
+    <com.android.settings.spa.preference.ComposeGroupSectionPreference
         android:key="app_data_usage_summary"
         settings:controller="com.android.settings.datausage.AppDataUsageSummaryController"/>
 
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index 779555b..f491055 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -42,7 +42,7 @@
         android:order="-7"
         android:title="@string/nfc_quick_toggle_title"
         settings:controller="com.android.settings.connecteddevice.NfcAndPaymentFragmentController"
-        settings:searchable="false"
+        settings:searchable="true"
         settings:useAdminDisabledSummary="true"
         settings:userRestriction="no_near_field_communication_radio" />
 
diff --git a/res/xml/date_time_prefs.xml b/res/xml/date_time_prefs.xml
index d8643be..fe0fd7e 100644
--- a/res/xml/date_time_prefs.xml
+++ b/res/xml/date_time_prefs.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!-- Copyright (C) 2024 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.
@@ -94,10 +94,6 @@
         android:key="time_format_preference_category"
         android:title="@string/time_format_category_title"
         settings:keywords="@string/keywords_time_format">
-        <SwitchPreferenceCompat
-            android:key="auto_24hour"
-            android:title="@string/date_time_24hour_auto"
-            settings:controller="com.android.settings.datetime.AutoTimeFormatPreferenceController" />
 
         <SwitchPreferenceCompat
             android:key="24 hour"
diff --git a/res/xml/date_time_prefs_revamped.xml b/res/xml/date_time_prefs_revamped.xml
deleted file mode 100644
index fe0fd7e..0000000
--- a/res/xml/date_time_prefs_revamped.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 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/date_and_time"
-    settings:keywords="@string/keywords_date_and_time">
-
-    <com.android.settingslib.RestrictedSwitchPreference
-        android:key="auto_time"
-        android:title="@string/date_time_auto"
-        android:summary="@string/summary_placeholder"
-        settings:userRestriction="no_config_date_time"
-        settings:controller="com.android.settings.datetime.AutoTimePreferenceController" />
-
-    <com.android.settingslib.RestrictedPreference
-        android:key="date"
-        android:title="@string/date_time_set_date_title"
-        android:summary="@string/summary_placeholder"
-        settings:userRestriction="no_config_date_time"
-        settings:controller="com.android.settings.datetime.DatePreferenceController" />
-
-    <com.android.settingslib.RestrictedPreference
-        android:key="time"
-        android:title="@string/date_time_set_time_title"
-        android:summary="@string/summary_placeholder"
-        settings:userRestriction="no_config_date_time"
-        settings:controller="com.android.settings.datetime.TimePreferenceController" />
-
-    <PreferenceCategory
-        android:key="timezone_preference_category"
-        android:title="@string/date_time_set_timezone_title">
-
-        <com.android.settingslib.RestrictedSwitchPreference
-            android:key="auto_zone"
-            android:title="@string/zone_auto_title"
-            android:summary="@string/summary_placeholder"
-            settings:userRestriction="no_config_date_time"
-            settings:controller="com.android.settings.datetime.AutoTimeZonePreferenceController" />
-
-        <com.android.settingslib.widget.BannerMessagePreference
-            android:key="location_time_zone_detection_status"
-            android:title="@string/location_time_zone_detection_status_title"
-            settings:controller="com.android.settings.datetime.LocationProviderStatusPreferenceController"/>
-
-        <!-- This preference gets removed if location-based time zone detection is not supported -->
-        <SwitchPreferenceCompat
-            android:key="location_time_zone_detection"
-            android:title="@string/location_time_zone_detection_toggle_title"
-            android:summary="@string/summary_placeholder"
-            settings:controller="com.android.settings.datetime.LocationTimeZoneDetectionPreferenceController"/>
-
-        <com.android.settingslib.RestrictedPreference
-            android:key="timezone"
-            android:title="@string/date_time_set_timezone_title"
-            android:summary="@string/summary_placeholder"
-            android:fragment="com.android.settings.datetime.timezone.TimeZoneSettings"
-            settings:userRestriction="no_config_date_time"
-            settings:keywords="@string/keywords_time_zone"
-            settings:controller="com.android.settings.datetime.TimeZonePreferenceController" />
-
-    </PreferenceCategory>
-
-    <!-- An optional preference category for feedback. Only displayed up if enabled via flags and config. -->
-    <PreferenceCategory
-            android:key="time_feedback_preference_category"
-            android:title="@string/time_feedback_category_title"
-            settings:keywords="@string/keywords_time_feedback_category"
-            settings:controller="com.android.settings.datetime.TimeFeedbackPreferenceCategoryController">
-
-        <Preference
-                android:key="time_feedback"
-                android:title="@string/time_feedback_title"
-                settings:keywords="@string/keywords_time_feedback"
-                settings:controller="com.android.settings.datetime.TimeFeedbackPreferenceController" />
-
-    </PreferenceCategory>
-
-    <PreferenceCategory
-        android:key="time_format_preference_category"
-        android:title="@string/time_format_category_title"
-        settings:keywords="@string/keywords_time_format">
-
-        <SwitchPreferenceCompat
-            android:key="24 hour"
-            android:title="@string/date_time_24hour"
-            settings:controller="com.android.settings.datetime.TimeFormatPreferenceController" />
-    </PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/res/xml/language_and_region_settings.xml b/res/xml/language_and_region_settings.xml
new file mode 100644
index 0000000..5626f22
--- /dev/null
+++ b/res/xml/language_and_region_settings.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 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/language_and_region_settings"
+    android:key="language_and_region_settings">
+  <PreferenceCategory
+      android:key="languages_category"
+      android:title="@string/locale_picker_category_title">
+    <Preference
+        android:key="phone_language"
+        android:title="@string/system_language"
+        android:fragment="com.android.settings.localepicker.LocaleListEditor"
+        settings:controller="com.android.settings.language.PhoneLanguagePreferenceController" />
+  </PreferenceCategory>
+
+  <PreferenceCategory
+      android:key="more_language_settings_category"
+      android:title="@string/more_language_settings_category"
+      settings:controller="com.android.settings.language.MoreLanguagesSettingsCategoryController">
+    <Preference
+        android:key="apps_language_in_more_language_settings"
+        android:title="@string/app_locales_picker_menu_title"
+        android:summary="@string/app_locale_picker_summary"
+        android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
+        settings:controller="com.android.settings.applications.appinfo.NewAppsLocalePreferenceController">
+      <extra
+          android:name="classname"
+          android:value="com.android.settings.applications.appinfo.AppLocaleDetails" />
+    </Preference>
+  </PreferenceCategory>
+
+  <PreferenceCategory
+      android:key="regional_preferences_category"
+      android:title="@string/regional_preferences_category_title"
+      settings:controller="com.android.settings.regionalpreferences.RegionalPreferencesCategoryController">
+
+    <Preference
+        android:key="temperature_preference"
+        android:title="@string/temperature_preferences_title"
+        android:summary="@string/default_string_of_regional_preference"
+        settings:controller="com.android.settings.regionalpreferences.NewTemperatureUnitController"
+        settings:fragment="com.android.settings.regionalpreferences.TemperatureUnitFragment">
+      <extra
+          android:name="arg_key_regional_preference"
+          android:value="mu"/>
+    </Preference>
+
+    <Preference
+        android:key="key_measurement_system"
+        android:title="@string/measurement_system_preferences_title"
+        android:summary="@string/default_string_of_regional_preference"
+        settings:controller="com.android.settings.regionalpreferences.MeasurementSystemController"
+        settings:fragment="com.android.settings.regionalpreferences.MeasurementSystemItemFragment">
+      <extra
+          android:name="arg_key_regional_preference"
+          android:value="ms"/>
+    </Preference>
+
+    <Preference
+        android:key="first_day_of_week_preference"
+        android:title="@string/first_day_of_week_preferences_title"
+        android:summary="@string/default_string_of_regional_preference"
+        settings:controller="com.android.settings.regionalpreferences.NewFirstDayOfWeekController"
+        settings:fragment="com.android.settings.regionalpreferences.FirstDayOfWeekItemFragment">
+      <extra
+          android:name="arg_key_regional_preference"
+          android:value="fw"/>
+    </Preference>
+
+    <Preference
+        android:key="numbering_system_preference"
+        android:title="@string/numbers_preferences_title"
+        android:summary="@string/default_string_of_regional_preference"
+        settings:controller="com.android.settings.regionalpreferences.NewNumberingSystemController"
+        settings:fragment="com.android.settings.regionalpreferences.NumberingPreferencesFragment">
+      <extra
+          android:name="arg_key_regional_preference"
+          android:value="arg_value_language_select"/>
+    </Preference>
+  </PreferenceCategory>
+
+  <PreferenceCategory
+      android:key="speech_category"
+      android:title="@string/speech_category_title">
+    <com.android.settings.widget.GearPreference
+        android:key="voice_input_settings"
+        android:title="@string/voice_input_settings_title"
+        android:fragment="com.android.settings.language.DefaultVoiceInputPicker" />
+
+    <Preference
+        android:key="on_device_recognition_settings"
+        android:title="@string/on_device_recognition_settings_title"
+        android:summary="@string/on_device_recognition_settings_summary"
+        settings:controller=
+            "com.android.settings.language.OnDeviceRecognitionPreferenceController" />
+
+    <Preference
+        android:key="tts_settings_summary"
+        android:title="@string/tts_settings_title"
+        android:fragment="com.android.settings.tts.TextToSpeechSettings"
+        settings:searchable="false"/>
+  </PreferenceCategory>
+
+  <com.android.settingslib.widget.FooterPreference
+      android:key="new_regional_pref_footer"
+      android:title="@string/title_regional_pref_footer"
+      android:selectable="false"
+      settings:searchable="false"
+      settings:controller="com.android.settings.regionalpreferences.NewRegionalFooterPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/language_settings.xml b/res/xml/language_settings.xml
index 44a195c..eccbbc1 100644
--- a/res/xml/language_settings.xml
+++ b/res/xml/language_settings.xml
@@ -50,72 +50,6 @@
     </PreferenceCategory>
 
     <PreferenceCategory
-        android:key="more_language_settings_category"
-        android:title="@string/more_language_settings_category"
-        settings:controller="com.android.settings.language.MoreLanguagesSettingsCategoryController">
-        <Preference
-            android:key="apps_language_in_more_language_settings"
-            android:title="@string/app_locales_picker_menu_title"
-            android:summary="@string/app_locale_picker_summary"
-            android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
-            settings:controller="com.android.settings.applications.appinfo.NewAppsLocalePreferenceController">
-            <extra
-                android:name="classname"
-                android:value="com.android.settings.applications.appinfo.AppLocaleDetails" />
-        </Preference>
-    </PreferenceCategory>
-
-    <PreferenceCategory
-        android:key="regional_preferences_category"
-        android:title="@string/regional_preferences_category_title"
-        settings:controller="com.android.settings.regionalpreferences.RegionalPreferencesCategoryController">
-
-        <Preference
-            android:key="temperature_preference"
-            android:title="@string/temperature_preferences_title"
-            android:summary="@string/default_string_of_regional_preference"
-            settings:controller="com.android.settings.regionalpreferences.NewTemperatureUnitController"
-            settings:fragment="com.android.settings.regionalpreferences.TemperatureUnitFragment">
-            <extra
-                android:name="arg_key_regional_preference"
-                android:value="mu"/>
-        </Preference>
-
-        <Preference
-            android:key="key_measurement_system"
-            android:title="@string/measurement_system_preferences_title"
-            android:summary="@string/default_string_of_regional_preference"
-            settings:controller="com.android.settings.regionalpreferences.MeasurementSystemController"
-            settings:fragment="com.android.settings.regionalpreferences.MeasurementSystemItemFragment">
-            <extra
-                android:name="arg_key_regional_preference"
-                android:value="ms"/>
-        </Preference>
-
-        <Preference
-            android:key="first_day_of_week_preference"
-            android:title="@string/first_day_of_week_preferences_title"
-            android:summary="@string/default_string_of_regional_preference"
-            settings:controller="com.android.settings.regionalpreferences.NewFirstDayOfWeekController"
-            settings:fragment="com.android.settings.regionalpreferences.FirstDayOfWeekItemFragment">
-            <extra
-                android:name="arg_key_regional_preference"
-                android:value="fw"/>
-        </Preference>
-
-        <Preference
-            android:key="numbering_system_preference"
-            android:title="@string/numbers_preferences_title"
-            android:summary="@string/default_string_of_regional_preference"
-            settings:controller="com.android.settings.regionalpreferences.NewNumberingSystemController"
-            settings:fragment="com.android.settings.regionalpreferences.NumberingPreferencesFragment">
-            <extra
-                android:name="arg_key_regional_preference"
-                android:value="arg_value_language_select"/>
-        </Preference>
-    </PreferenceCategory>
-
-    <PreferenceCategory
         android:key="speech_category"
         android:title="@string/speech_category_title">
         <com.android.settings.widget.GearPreference
@@ -136,11 +70,4 @@
             android:fragment="com.android.settings.tts.TextToSpeechSettings"
             settings:searchable="false"/>
     </PreferenceCategory>
-
-    <com.android.settingslib.widget.FooterPreference
-        android:key="new_regional_pref_footer"
-        android:title="@string/title_regional_pref_footer"
-        android:selectable="false"
-        settings:searchable="false"
-        settings:controller="com.android.settings.regionalpreferences.NewRegionalFooterPreferenceController"/>
 </PreferenceScreen>
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index a1a2276..8c948ff 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -18,7 +18,7 @@
     xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:key="mobile_network_pref_screen">
 
-    <com.android.settings.spa.preference.ComposeMainSwitchPreference
+    <com.android.settings.spa.preference.ComposeGroupSectionPreference
         android:key="use_sim_switch"
         settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>
 
diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml
index 9c7f001..a9d7444 100644
--- a/res/xml/system_dashboard_fragment.xml
+++ b/res/xml/system_dashboard_fragment.xml
@@ -30,6 +30,15 @@
         settings:controller="com.android.settings.language.LanguagePreferenceController"/>
 
     <Preference
+        android:key="language_and_region_settings"
+        android:title="@string/language_and_region_settings"
+        android:summary="@string/languages_setting_summary"
+        android:icon="@drawable/ic_settings_languages"
+        android:order="-260"
+        android:fragment="com.android.settings.language.LanguageAndRegionSettings"
+        settings:controller="com.android.settings.language.LanguageAndRegionPreferenceController"/>
+
+    <Preference
         android:key="Keyboard_settings"
         android:title="@string/keyboard_settings"
         android:icon="@drawable/ic_settings_keyboards"
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
index 09e2d97..3bda864 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
@@ -97,17 +97,6 @@
     }
 
     @Override
-    protected ComponentName getTileComponentName() {
-        return AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME;
-    }
-
-    @Override
-    protected CharSequence getTileTooltipContent(int type) {
-        // No tooltip to be shown
-        return null;
-    }
-
-    @Override
     protected boolean showGeneralCategory() {
         // Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY.
         // In order to modify that, we need to use our own PreferenceCategory for this page.
diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
index 23c3fd4..2a22902 100644
--- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
@@ -20,10 +20,8 @@
 import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
 import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY;
-import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_QS_TOOLTIP_TYPE;
 
 import android.annotation.SuppressLint;
-import android.app.Activity;
 import android.app.Dialog;
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
@@ -44,7 +42,6 @@
 
 import com.android.internal.accessibility.common.ShortcutConstants;
 import com.android.settings.R;
-import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
 import com.android.settings.dashboard.RestrictedDashboardFragment;
 
@@ -60,16 +57,12 @@
 public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment
         implements ShortcutPreference.OnClickCallback {
     private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
-    protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
 
     protected ShortcutPreference mShortcutPreference;
     protected Dialog mDialog;
     private AccessibilityManager.TouchExplorationStateChangeListener
             mTouchExplorationStateChangeListener;
     private AccessibilitySettingsContentObserver mSettingsContentObserver;
-    private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
-    private boolean mNeedsQSTooltipReshow = false;
-    private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
 
     public AccessibilityShortcutPreferenceFragment(String restrictionKey) {
         super(restrictionKey);
@@ -81,26 +74,10 @@
     /** Returns the accessibility feature name. */
     protected abstract CharSequence getLabelName();
 
-    /** Returns the accessibility tile component name. */
-    protected abstract ComponentName getTileComponentName();
-
-    /** Returns the accessibility tile tooltip content. */
-    protected abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type);
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        // Restore the user shortcut type and tooltip.
-        if (savedInstanceState != null) {
-            if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
-                mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
-            }
-            if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_TYPE)) {
-                mNeedsQSTooltipType = savedInstanceState.getInt(KEY_SAVED_QS_TOOLTIP_TYPE);
-            }
-        }
-
         final int resId = getPreferenceScreenResId();
         if (resId <= 0) {
             final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -142,21 +119,6 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        // Reshow tooltip when activity recreate, such as rotate device.
-        if (mNeedsQSTooltipReshow) {
-            view.post(() -> {
-                final Activity activity = getActivity();
-                if (activity != null && !activity.isFinishing()) {
-                    showQuickSettingsTooltipIfNeeded();
-                }
-            });
-        }
-    }
-
-    @Override
     public void onResume() {
         super.onResume();
 
@@ -178,16 +140,6 @@
     }
 
     @Override
-    public void onSaveInstanceState(Bundle outState) {
-        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
-        if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
-            outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
-            outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType);
-        }
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
     public Dialog onCreateDialog(int dialogId) {
         switch (dialogId) {
             case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
@@ -289,7 +241,6 @@
      */
     private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
         dialog.dismiss();
-        showQuickSettingsTooltipIfNeeded();
     }
 
     @VisibleForTesting
@@ -363,26 +314,6 @@
     }
 
     /**
-     * Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only
-     * shows once.
-     *
-     * @param type The quick settings tooltip type
-     */
-    protected void showQuickSettingsTooltipIfNeeded(@QuickSettingsTooltipType int type) {
-        mNeedsQSTooltipType = type;
-        showQuickSettingsTooltipIfNeeded();
-    }
-
-    /**
-     * @deprecated made obsolete by quick settings rollout.
-     *
-     * (TODO 367414968: finish removal.)
-     */
-    @Deprecated
-    private void showQuickSettingsTooltipIfNeeded() {
-    }
-
-    /**
      * Returns the user preferred shortcut types or the default shortcut types if not set
      */
     @ShortcutConstants.UserShortcutType
diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
index e49078b..f729cc7 100644
--- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
@@ -72,11 +72,6 @@
     }
 
     @Override
-    protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
-        // Do nothing.
-    }
-
-    @Override
     protected void onProcessArguments(Bundle arguments) {
         super.onProcessArguments(arguments);
         mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
diff --git a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
index e286225..b779f9d 100644
--- a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
@@ -107,10 +107,6 @@
         if (enabled == isEnabled) {
             return;
         }
-
-        if (enabled) {
-            showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
-        }
         logAccessibilityServiceEnabled(mComponentName, enabled);
         Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
     }
diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
index 5b2df5a..fe51e69 100644
--- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
@@ -158,10 +158,6 @@
         if (enabled == isEnabled) {
             return;
         }
-
-        if (enabled) {
-            showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
-        }
         logAccessibilityServiceEnabled(mComponentName, enabled);
         Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
     }
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 65a1cd4..49f22bf 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -21,7 +21,6 @@
 import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
 
-import android.app.Activity;
 import android.app.Dialog;
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
@@ -85,8 +84,6 @@
     public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
     protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro";
     protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
-    protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
-    protected static final String KEY_SAVED_QS_TOOLTIP_TYPE = "qs_tooltip_type";
     protected static final String KEY_ANIMATED_IMAGE = "animated_image";
     // For html description of accessibility service, must follow the rule, such as
     // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
@@ -112,10 +109,6 @@
     private CharSequence mDescription;
     private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
     private AccessibilitySettingsContentObserver mSettingsContentObserver;
-
-    private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
-    private boolean mNeedsQSTooltipReshow = false;
-    private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
     private ImageView mImageGetterCacheView;
     protected final Html.ImageGetter mImageGetter = (String str) -> {
         if (str != null && str.startsWith(IMG_PREFIX)) {
@@ -133,16 +126,6 @@
         super.onCreate(savedInstanceState);
 
         onProcessArguments(getArguments());
-        // Restore the user shortcut type and tooltip.
-        if (savedInstanceState != null) {
-            if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
-                mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
-            }
-            if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_TYPE)) {
-                mNeedsQSTooltipType = savedInstanceState.getInt(KEY_SAVED_QS_TOOLTIP_TYPE);
-            }
-        }
-
         final int resId = getPreferenceScreenResId();
         if (resId <= 0) {
             final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -227,16 +210,6 @@
         final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar();
         switchBar.hide();
 
-        // Reshow tooltip when activity recreate, such as rotate device.
-        if (mNeedsQSTooltipReshow) {
-            view.post(() -> {
-                final Activity activity = getActivity();
-                if (activity != null && !activity.isFinishing()) {
-                    showQuickSettingsTooltipIfNeeded();
-                }
-            });
-        }
-
         writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(getContext());
     }
 
@@ -262,23 +235,9 @@
     }
 
     @Override
-    public void onSaveInstanceState(Bundle outState) {
-        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
-        if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
-            outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
-            outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType);
-        }
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
     public void onDestroyView() {
         super.onDestroyView();
         removeActionBarToggleSwitch();
-        final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
-        if (isTooltipWindowShowing) {
-            mTooltipWindow.dismiss();
-        }
     }
 
     @Override
@@ -314,7 +273,12 @@
     /** Returns the accessibility tile component name. */
     abstract ComponentName getTileComponentName();
 
-    /** Returns the accessibility tile tooltip content. */
+    /** Returns the accessibility tile component name.
+     *
+     * @deprecated unused, as this class no longer displays tile tooltips.
+     *
+     * (TODO 367414968: finish removal.)*/
+    @Deprecated
     abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type);
 
     protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
@@ -332,9 +296,6 @@
     }
 
     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
-        if (enabled) {
-            showQuickSettingsTooltipIfNeeded();
-        }
     }
 
     protected void onInstallSwitchPreferenceToggleSwitch() {
@@ -646,7 +607,6 @@
      */
     private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
         dialog.dismiss();
-        showQuickSettingsTooltipIfNeeded();
     }
 
     protected void updateShortcutPreferenceData() {
@@ -737,26 +697,6 @@
         }
     }
 
-    /**
-     * Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only
-     * shows once.
-     *
-     * @param type The quick settings tooltip type
-     */
-    protected void showQuickSettingsTooltipIfNeeded(@QuickSettingsTooltipType int type) {
-        mNeedsQSTooltipType = type;
-        showQuickSettingsTooltipIfNeeded();
-    }
-
-    /**
-     * @deprecated made obsolete by quick settings rollout.
-     *
-     * (TODO 367414968: finish removal.)
-     */
-    @Deprecated
-    private void showQuickSettingsTooltipIfNeeded() {
-    }
-
     /** Returns user visible name of the tile by given {@link ComponentName}. */
     protected CharSequence loadTileLabel(Context context, ComponentName componentName) {
         final PackageManager packageManager = context.getPackageManager();
diff --git a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
index 16911f6..ff14021 100644
--- a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
@@ -149,9 +149,6 @@
 
     @Override
     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
-        if (enabled) {
-            showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
-        }
         logAccessibilityServiceEnabled(mComponentName, enabled);
         mColorDisplayManager.setReduceBrightColorsActivated(enabled);
     }
diff --git a/src/com/android/settings/bluetooth/AmbientVolumePreference.java b/src/com/android/settings/bluetooth/AmbientVolumePreference.java
new file mode 100644
index 0000000..e916c04
--- /dev/null
+++ b/src/com/android/settings/bluetooth/AmbientVolumePreference.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2024 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.view.View.GONE;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+import static android.view.View.VISIBLE;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settings.widget.SeekBarPreference;
+
+import com.google.common.primitives.Ints;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A preference group of ambient volume controls.
+ *
+ * <p> It consists of a header with an expand icon and volume sliders for unified control and
+ * separated control for devices in the same set. Toggle the expand icon will make the UI switch
+ * between unified and separated control.
+ */
+public class AmbientVolumePreference extends PreferenceGroup {
+
+    /** Interface definition for a callback to be invoked when the icon is clicked. */
+    public interface OnIconClickListener {
+        /** Called when the expand icon is clicked. */
+        void onExpandIconClick();
+
+        /** Called when the ambient volume icon is clicked. */
+        void onAmbientVolumeIconClick();
+    };
+
+    static final float ROTATION_COLLAPSED = 0f;
+    static final float ROTATION_EXPANDED = 180f;
+    static final int AMBIENT_VOLUME_LEVEL_MIN = 0;
+    static final int AMBIENT_VOLUME_LEVEL_MAX = 24;
+    static final int AMBIENT_VOLUME_LEVEL_DEFAULT = 24;
+    static final int SIDE_UNIFIED = 999;
+    static final List<Integer> VALID_SIDES = List.of(SIDE_UNIFIED, SIDE_LEFT, SIDE_RIGHT);
+
+    @Nullable
+    private OnIconClickListener mListener;
+    @Nullable
+    private View mExpandIcon;
+    @Nullable
+    private ImageView mVolumeIcon;
+    private boolean mExpandable = true;
+    private boolean mExpanded = false;
+    private boolean mMutable = false;
+    private boolean mMuted = false;
+    private Map<Integer, SeekBarPreference> mSideToSliderMap = new ArrayMap<>();
+
+    /**
+     * Ambient volume level for hearing device ambient control icon
+     * <p>
+     * This icon visually represents the current ambient gain setting.
+     * It displays separate levels for the left and right sides, each with 5 levels ranging from 0
+     * to 4.
+     * <p>
+     * To represent the combined left/right levels with a single value, the following calculation
+     * is used:
+     *      finalLevel = (leftLevel * 5) + rightLevel
+     * For example:
+     * <ul>
+     *    <li>If left level is 2 and right level is 3, the final level will be 13 (2 * 5 + 3)</li>
+     *    <li>If both left and right levels are 0, the final level will be 0</li>
+     *    <li>If both left and right levels are 4, the final level will be 24</li>
+     * </ul>
+     */
+    private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+
+    public AmbientVolumePreference(@NonNull Context context) {
+        super(context, null);
+        setLayoutResource(R.layout.preference_ambient_volume);
+        setIcon(com.android.settingslib.R.drawable.ic_ambient_volume);
+        setTitle(R.string.bluetooth_ambient_volume_control);
+        setSelectable(false);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        holder.setDividerAllowedAbove(false);
+        holder.setDividerAllowedBelow(false);
+
+        mVolumeIcon = holder.itemView.requireViewById(com.android.internal.R.id.icon);
+        mVolumeIcon.getDrawable().mutate().setTint(getContext().getColor(
+                com.android.internal.R.color.materialColorOnPrimaryContainer));
+        final View iconView = holder.itemView.requireViewById(R.id.icon_frame);
+        iconView.setOnClickListener(v -> {
+            if (!mMutable) {
+                return;
+            }
+            setMuted(!mMuted);
+            if (mListener != null) {
+                mListener.onAmbientVolumeIconClick();
+            }
+        });
+        updateVolumeIcon();
+
+        mExpandIcon = holder.itemView.requireViewById(R.id.expand_icon);
+        mExpandIcon.setOnClickListener(v -> {
+            setExpanded(!mExpanded);
+            if (mListener != null) {
+                mListener.onExpandIconClick();
+            }
+        });
+        updateExpandIcon();
+    }
+
+    void setExpandable(boolean expandable) {
+        mExpandable = expandable;
+        if (!mExpandable) {
+            setExpanded(false);
+        }
+        updateExpandIcon();
+    }
+
+    boolean isExpandable() {
+        return mExpandable;
+    }
+
+    void setExpanded(boolean expanded) {
+        if (!mExpandable && expanded) {
+            return;
+        }
+        mExpanded = expanded;
+        updateExpandIcon();
+        updateLayout();
+    }
+
+    boolean isExpanded() {
+        return mExpanded;
+    }
+
+    void setMutable(boolean mutable) {
+        mMutable = mutable;
+        if (!mMutable) {
+            mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+            setMuted(false);
+        }
+        updateVolumeIcon();
+    }
+
+    boolean isMutable() {
+        return mMutable;
+    }
+
+    void setMuted(boolean muted) {
+        if (!mMutable && muted) {
+            return;
+        }
+        mMuted = muted;
+        if (mMutable && mMuted) {
+            for (SeekBarPreference slider : mSideToSliderMap.values()) {
+                slider.setProgress(slider.getMin());
+            }
+        }
+        updateVolumeIcon();
+    }
+
+    boolean isMuted() {
+        return mMuted;
+    }
+
+    void setOnIconClickListener(@Nullable OnIconClickListener listener) {
+        mListener = listener;
+    }
+
+    void setSliders(Map<Integer, SeekBarPreference> sideToSliderMap) {
+        mSideToSliderMap = sideToSliderMap;
+        for (SeekBarPreference preference : sideToSliderMap.values()) {
+            if (findPreference(preference.getKey()) == null) {
+                addPreference(preference);
+            }
+        }
+        updateLayout();
+    }
+
+    void setSliderEnabled(int side, boolean enabled) {
+        SeekBarPreference slider = mSideToSliderMap.get(side);
+        if (slider != null && slider.isEnabled() != enabled) {
+            slider.setEnabled(enabled);
+            updateLayout();
+        }
+    }
+
+    void setSliderValue(int side, int value) {
+        SeekBarPreference slider = mSideToSliderMap.get(side);
+        if (slider != null && slider.getProgress() != value) {
+            slider.setProgress(value);
+            updateVolumeLevel();
+        }
+    }
+
+    void setSliderRange(int side, int min, int max) {
+        SeekBarPreference slider = mSideToSliderMap.get(side);
+        if (slider != null) {
+            slider.setMin(min);
+            slider.setMax(max);
+        }
+    }
+
+    void updateLayout() {
+        mSideToSliderMap.forEach((side, slider) -> {
+            if (side == SIDE_UNIFIED) {
+                slider.setVisible(!mExpanded);
+            } else {
+                slider.setVisible(mExpanded);
+            }
+            if (!slider.isEnabled()) {
+                slider.setProgress(slider.getMin());
+            }
+        });
+        updateVolumeLevel();
+    }
+
+    private void updateVolumeLevel() {
+        int leftLevel, rightLevel;
+        if (mExpanded) {
+            leftLevel = getVolumeLevel(SIDE_LEFT);
+            rightLevel = getVolumeLevel(SIDE_RIGHT);
+        } else {
+            final int unifiedLevel = getVolumeLevel(SIDE_UNIFIED);
+            leftLevel = unifiedLevel;
+            rightLevel = unifiedLevel;
+        }
+        mVolumeLevel = Ints.constrainToRange(leftLevel * 5 + rightLevel,
+                AMBIENT_VOLUME_LEVEL_MIN, AMBIENT_VOLUME_LEVEL_MAX);
+        updateVolumeIcon();
+    }
+
+    private int getVolumeLevel(int side) {
+        SeekBarPreference slider = mSideToSliderMap.get(side);
+        if (slider == null || !slider.isEnabled()) {
+            return 0;
+        }
+        final double min = slider.getMin();
+        final double max = slider.getMax();
+        final double levelGap = (max - min) / 4.0;
+        final int value = slider.getProgress();
+        return (int) Math.ceil((value - min) / levelGap);
+    }
+
+    private void updateExpandIcon() {
+        if (mExpandIcon == null) {
+            return;
+        }
+        mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
+        mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
+        if (mExpandable) {
+            final int stringRes = mExpanded
+                    ? R.string.bluetooth_ambient_volume_control_collapse
+                    : R.string.bluetooth_ambient_volume_control_expand;
+            mExpandIcon.setContentDescription(getContext().getString(stringRes));
+        } else {
+            mExpandIcon.setContentDescription(null);
+        }
+    }
+
+    private void updateVolumeIcon() {
+        if (mVolumeIcon == null) {
+            return;
+        }
+        mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
+        if (mMutable) {
+            final int stringRes = mMuted
+                    ? R.string.bluetooth_ambient_volume_unmute
+                    : R.string.bluetooth_ambient_volume_mute;
+            mVolumeIcon.setContentDescription(getContext().getString(stringRes));
+            mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }  else {
+            mVolumeIcon.setContentDescription(null);
+            mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
new file mode 100644
index 0000000..f237ffe
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2024 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.AudioInputControl.MUTE_NOT_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+import static com.android.settings.bluetooth.AmbientVolumePreference.SIDE_UNIFIED;
+import static com.android.settings.bluetooth.AmbientVolumePreference.VALID_SIDES;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_AMBIENT_VOLUME;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.ArraySet;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.bluetooth.AmbientVolumeController;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
+import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.VolumeControlProfile;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+import java.util.Map;
+import java.util.Set;
+
+/** A {@link BluetoothDetailsController} that manages ambient volume control preferences. */
+public class BluetoothDetailsAmbientVolumePreferenceController extends
+        BluetoothDetailsController implements Preference.OnPreferenceChangeListener,
+        HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener, OnStart, OnStop,
+        AmbientVolumeController.AmbientVolumeControlCallback, BluetoothCallback {
+
+    private static final boolean DEBUG = true;
+    private static final String TAG = "AmbientPrefController";
+
+    static final String KEY_AMBIENT_VOLUME = "ambient_volume";
+    static final String KEY_AMBIENT_VOLUME_SLIDER = "ambient_volume_slider";
+    private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
+    private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
+
+    private final LocalBluetoothManager mBluetoothManager;
+    private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
+    private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
+    private final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
+    private final HearingDeviceLocalDataManager mLocalDataManager;
+    private final AmbientVolumeController mVolumeController;
+
+    @Nullable
+    private PreferenceCategory mDeviceControls;
+    @Nullable
+    private AmbientVolumePreference mPreference;
+    @Nullable
+    private Toast mToast;
+
+    public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
+            @NonNull LocalBluetoothManager manager,
+            @NonNull PreferenceFragmentCompat fragment,
+            @NonNull CachedBluetoothDevice device,
+            @NonNull Lifecycle lifecycle) {
+        super(context, fragment, device, lifecycle);
+        mBluetoothManager = manager;
+        mLocalDataManager = new HearingDeviceLocalDataManager(context);
+        mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
+                ThreadUtils.getBackgroundExecutor());
+        mVolumeController = new AmbientVolumeController(manager.getProfileManager(), this);
+    }
+
+    @VisibleForTesting
+    BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
+            @NonNull LocalBluetoothManager manager,
+            @NonNull PreferenceFragmentCompat fragment,
+            @NonNull CachedBluetoothDevice device,
+            @NonNull Lifecycle lifecycle,
+            @NonNull HearingDeviceLocalDataManager localSettings,
+            @NonNull AmbientVolumeController volumeController) {
+        super(context, fragment, device, lifecycle);
+        mBluetoothManager = manager;
+        mLocalDataManager = localSettings;
+        mVolumeController = volumeController;
+    }
+
+    @Override
+    protected void init(PreferenceScreen screen) {
+        mDeviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
+        if (mDeviceControls == null) {
+            return;
+        }
+        loadDevices();
+    }
+
+    @Override
+    public void onStart() {
+        ThreadUtils.postOnBackgroundThread(() -> {
+            mBluetoothManager.getEventManager().registerCallback(this);
+            mLocalDataManager.start();
+            mCachedDevices.forEach(device -> {
+                device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+                mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+                        device.getDevice());
+            });
+        });
+    }
+
+    @Override
+    public void onResume() {
+        refresh();
+    }
+
+    @Override
+    public void onPause() {
+    }
+
+    @Override
+    public void onStop() {
+        ThreadUtils.postOnBackgroundThread(() -> {
+            mBluetoothManager.getEventManager().unregisterCallback(this);
+            mLocalDataManager.stop();
+            mCachedDevices.forEach(device -> {
+                device.unregisterCallback(this);
+                mVolumeController.unregisterCallback(device.getDevice());
+            });
+        });
+    }
+
+    @Override
+    protected void refresh() {
+        if (!isAvailable()) {
+            return;
+        }
+        boolean shouldShowAmbientControl = isAmbientControlAvailable();
+        if (shouldShowAmbientControl) {
+            if (mPreference != null) {
+                mPreference.setVisible(true);
+            }
+            loadRemoteDataToUi();
+        } else {
+            if (mPreference != null) {
+                mPreference.setVisible(false);
+            }
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mCachedDevice.getProfiles().stream().anyMatch(
+                profile -> profile instanceof VolumeControlProfile);
+    }
+
+    @Nullable
+    @Override
+    public String getPreferenceKey() {
+        return KEY_AMBIENT_VOLUME;
+    }
+
+    @Override
+    public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
+        if (preference instanceof SeekBarPreference && newValue instanceof final Integer value) {
+            final int side = mSideToSliderMap.inverse().getOrDefault(preference, SIDE_INVALID);
+            if (DEBUG) {
+                Log.d(TAG, "onPreferenceChange: side=" + side + ", value=" + value);
+            }
+            setVolumeIfValid(side, value);
+
+            Runnable setAmbientRunnable = () -> {
+                if (side == SIDE_UNIFIED) {
+                    mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
+                } else {
+                    final BluetoothDevice device = mSideToDeviceMap.get(side);
+                    mVolumeController.setAmbient(device, value);
+                }
+            };
+
+            if (isControlMuted()) {
+                // User drag on the volume slider when muted. Unmute the devices first.
+                if (mPreference != null) {
+                    mPreference.setMuted(false);
+                }
+                for (BluetoothDevice device : mSideToDeviceMap.values()) {
+                    mVolumeController.setMuted(device, false);
+                }
+                // Restore the value before muted
+                loadLocalDataToUi();
+                // Delay set ambient on remote device since the immediately sequential command
+                // might get failed sometimes
+                mContext.getMainThreadHandler().postDelayed(setAmbientRunnable, 1000L);
+            } else {
+                setAmbientRunnable.run();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
+            int state, int bluetoothProfile) {
+        if (bluetoothProfile == BluetoothProfile.VOLUME_CONTROL
+                && state == BluetoothProfile.STATE_CONNECTED
+                && mCachedDevices.contains(cachedDevice)) {
+            // After VCP connected, AICS may not ready yet and still return invalid value, delay
+            // a while to wait AICS ready as a workaround
+            mContext.getMainThreadHandler().postDelayed(this::refresh, 1000L);
+        }
+    }
+
+    @Override
+    public void onDeviceAttributesChanged() {
+        mCachedDevices.forEach(device -> {
+            device.unregisterCallback(this);
+            mVolumeController.unregisterCallback(device.getDevice());
+        });
+        mContext.getMainExecutor().execute(() -> {
+            loadDevices();
+            if (!mCachedDevices.isEmpty()) {
+                refresh();
+            }
+            ThreadUtils.postOnBackgroundThread(() ->
+                    mCachedDevices.forEach(device -> {
+                        device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+                        mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+                                device.getDevice());
+                    })
+            );
+        });
+    }
+
+    @Override
+    public void onDeviceLocalDataChange(@NonNull String address, @Nullable Data data) {
+        if (data == null) {
+            // The local data is removed because the device is unpaired, do nothing
+            return;
+        }
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            if (device.getAnonymizedAddress().equals(address)) {
+                mContext.getMainExecutor().execute(() -> loadLocalDataToUi(device));
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onVolumeControlServiceConnected() {
+        mCachedDevices.forEach(
+                device -> mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+                        device.getDevice()));
+    }
+
+    @Override
+    public void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
+        if (DEBUG) {
+            Log.d(TAG, "onAmbientChanged, value:" + gainSettings + ", device:" + device);
+        }
+        Data data = mLocalDataManager.get(device);
+        boolean isInitiatedFromUi = (isControlExpanded() && data.ambient() == gainSettings)
+                || (!isControlExpanded() && data.groupAmbient() == gainSettings);
+        if (isInitiatedFromUi) {
+            // The change is initiated from UI, no need to update UI
+            return;
+        }
+
+        // We have to check if we need to expand the controls by getting all remote
+        // device's ambient value, delay for a while to wait all remote devices update
+        // to the latest value to avoid unnecessary expand action.
+        mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
+    }
+
+    @Override
+    public void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
+        if (DEBUG) {
+            Log.d(TAG, "onMuteChanged, mute:" + mute + ", device:" + device);
+        }
+        boolean isInitiatedFromUi = (isControlMuted() && mute == MUTE_MUTED)
+                || (!isControlMuted() && mute == MUTE_NOT_MUTED);
+        if (isInitiatedFromUi) {
+            // The change is initiated from UI, no need to update UI
+            return;
+        }
+
+        // We have to check if we need to mute the devices by getting all remote
+        // device's mute state, delay for a while to wait all remote devices update
+        // to the latest value.
+        mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
+    }
+
+    @Override
+    public void onCommandFailed(@NonNull BluetoothDevice device) {
+        Log.w(TAG, "onCommandFailed, device:" + device);
+        mContext.getMainExecutor().execute(() -> {
+            showErrorToast();
+            refresh();
+        });
+    }
+
+    private void loadDevices() {
+        mSideToDeviceMap.clear();
+        mCachedDevices.clear();
+        if (VALID_SIDES.contains(mCachedDevice.getDeviceSide())
+                && mCachedDevice.getBondState() == BOND_BONDED) {
+            mSideToDeviceMap.put(mCachedDevice.getDeviceSide(), mCachedDevice.getDevice());
+            mCachedDevices.add(mCachedDevice);
+        }
+        for (CachedBluetoothDevice memberDevice : mCachedDevice.getMemberDevice()) {
+            if (VALID_SIDES.contains(memberDevice.getDeviceSide())
+                    && memberDevice.getBondState() == BOND_BONDED) {
+                mSideToDeviceMap.put(memberDevice.getDeviceSide(), memberDevice.getDevice());
+                mCachedDevices.add(memberDevice);
+            }
+        }
+        createAmbientVolumePreference();
+        createSliderPreferences();
+        if (mPreference != null) {
+            mPreference.setExpandable(mSideToDeviceMap.size() > 1);
+            mPreference.setSliders((mSideToSliderMap));
+        }
+    }
+
+    private void createAmbientVolumePreference() {
+        if (mPreference != null || mDeviceControls == null) {
+            return;
+        }
+
+        mPreference = new AmbientVolumePreference(mDeviceControls.getContext());
+        mPreference.setKey(KEY_AMBIENT_VOLUME);
+        mPreference.setOrder(ORDER_AMBIENT_VOLUME);
+        mPreference.setOnIconClickListener(
+                new AmbientVolumePreference.OnIconClickListener() {
+                    @Override
+                    public void onExpandIconClick() {
+                        mSideToDeviceMap.forEach((s, d) -> {
+                            if (!isControlMuted()) {
+                                // Apply previous collapsed/expanded volume to remote device
+                                Data data = mLocalDataManager.get(d);
+                                int volume = isControlExpanded()
+                                        ? data.ambient() : data.groupAmbient();
+                                mVolumeController.setAmbient(d, volume);
+                            }
+                            // Update new value to local data
+                            mLocalDataManager.updateAmbientControlExpanded(d, isControlExpanded());
+                        });
+                    }
+
+                    @Override
+                    public void onAmbientVolumeIconClick() {
+                        if (!isControlMuted()) {
+                            loadLocalDataToUi();
+                        }
+                        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+                            mVolumeController.setMuted(device, isControlMuted());
+                        }
+                    }
+                });
+        if (mDeviceControls.findPreference(mPreference.getKey()) == null) {
+            mDeviceControls.addPreference(mPreference);
+        }
+    }
+
+    private void createSliderPreferences() {
+        mSideToDeviceMap.forEach((s, d) ->
+                createSliderPreference(s, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + s));
+        createSliderPreference(SIDE_UNIFIED, ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED);
+    }
+
+    private void createSliderPreference(int side, int order) {
+        if (mSideToSliderMap.containsKey(side) || mDeviceControls == null) {
+            return;
+        }
+        SeekBarPreference preference = new SeekBarPreference(mDeviceControls.getContext());
+        preference.setKey(KEY_AMBIENT_VOLUME_SLIDER + "_" + side);
+        preference.setOrder(order);
+        preference.setOnPreferenceChangeListener(this);
+        if (side == SIDE_LEFT) {
+            preference.setTitle(mContext.getString(R.string.bluetooth_ambient_volume_control_left));
+        } else if (side == SIDE_RIGHT) {
+            preference.setTitle(
+                    mContext.getString(R.string.bluetooth_ambient_volume_control_right));
+        }
+        mSideToSliderMap.put(side, preference);
+    }
+
+    /** Refreshes the control UI visibility and enabled state. */
+    private void refreshControlUi() {
+        if (mPreference != null) {
+            boolean isAnySliderEnabled = false;
+            for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
+                final int side = entry.getKey();
+                final BluetoothDevice device = entry.getValue();
+                final boolean enabled = isDeviceConnectedToVcp(device)
+                        && mVolumeController.isAmbientControlAvailable(device);
+                isAnySliderEnabled |= enabled;
+                mPreference.setSliderEnabled(side, enabled);
+            }
+            mPreference.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
+            mPreference.updateLayout();
+        }
+    }
+
+    /** Sets the volume to the corresponding control slider. */
+    private void setVolumeIfValid(int side, int volume) {
+        if (volume == INVALID_VOLUME) {
+            return;
+        }
+        if (mPreference != null) {
+            mPreference.setSliderValue(side, volume);
+        }
+        // Update new value to local data
+        if (side == SIDE_UNIFIED) {
+            mSideToDeviceMap.forEach((s, d) -> mLocalDataManager.updateGroupAmbient(d, volume));
+        } else {
+            mLocalDataManager.updateAmbient(mSideToDeviceMap.get(side), volume);
+        }
+    }
+
+    private void loadLocalDataToUi() {
+        mSideToDeviceMap.forEach((s, d) -> loadLocalDataToUi(d));
+    }
+
+    private void loadLocalDataToUi(BluetoothDevice device) {
+        final Data data = mLocalDataManager.get(device);
+        if (DEBUG) {
+            Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
+        }
+        final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
+        if (isDeviceConnectedToVcp(device) && !isControlMuted()) {
+            setVolumeIfValid(side, data.ambient());
+            setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
+        }
+        setControlExpanded(data.ambientControlExpanded());
+        refreshControlUi();
+    }
+
+    private void loadRemoteDataToUi() {
+        BluetoothDevice leftDevice = mSideToDeviceMap.get(SIDE_LEFT);
+        AmbientVolumeController.RemoteAmbientState leftState =
+                mVolumeController.refreshAmbientState(leftDevice);
+        BluetoothDevice rightDevice = mSideToDeviceMap.get(SIDE_RIGHT);
+        AmbientVolumeController.RemoteAmbientState rightState =
+                mVolumeController.refreshAmbientState(rightDevice);
+        if (DEBUG) {
+            Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
+        }
+
+        if (mPreference != null) {
+            mSideToDeviceMap.forEach((side, device) -> {
+                int ambientMax = mVolumeController.getAmbientMax(device);
+                int ambientMin = mVolumeController.getAmbientMin(device);
+                if (ambientMin != ambientMax) {
+                    mPreference.setSliderRange(side, ambientMin, ambientMax);
+                    mPreference.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
+                }
+            });
+        }
+
+        // Update ambient volume
+        final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
+        final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
+        if (isControlExpanded()) {
+            setVolumeIfValid(SIDE_LEFT, leftAmbient);
+            setVolumeIfValid(SIDE_RIGHT, rightAmbient);
+        } else {
+            if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
+                    && rightAmbient != INVALID_VOLUME) {
+                setVolumeIfValid(SIDE_LEFT, leftAmbient);
+                setVolumeIfValid(SIDE_RIGHT, rightAmbient);
+                setControlExpanded(true);
+            } else {
+                int unifiedAmbient = leftAmbient != INVALID_VOLUME ? leftAmbient : rightAmbient;
+                setVolumeIfValid(SIDE_UNIFIED, unifiedAmbient);
+            }
+        }
+        // Initialize local data between side and group value
+        initLocalDataIfNeeded();
+
+        // Update mute state
+        boolean mutable = true;
+        boolean muted = true;
+        if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
+            mutable &= leftState.isMutable();
+            muted &= leftState.isMuted();
+        }
+        if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
+            mutable &= rightState.isMutable();
+            muted &= rightState.isMuted();
+        }
+        if (mPreference != null) {
+            mPreference.setMutable(mutable);
+            mPreference.setMuted(muted);
+        }
+
+        // Ensure remote device mute state is synced
+        syncMuteStateIfNeeded(leftDevice, leftState, muted);
+        syncMuteStateIfNeeded(rightDevice, rightState, muted);
+
+        refreshControlUi();
+    }
+
+    /** Check if any device in the group has valid ambient control points */
+    private boolean isAmbientControlAvailable() {
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            // Found ambient local data for this device, show the ambient control
+            if (mLocalDataManager.get(device).hasAmbientData()) {
+                return true;
+            }
+            // Found remote ambient control points on this device, show the ambient control
+            if (mVolumeController.isAmbientControlAvailable(device)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isControlExpanded() {
+        return mPreference != null && mPreference.isExpanded();
+    }
+
+    private void setControlExpanded(boolean expanded) {
+        if (mPreference != null && mPreference.isExpanded() != expanded) {
+            mPreference.setExpanded(expanded);
+        }
+        mSideToDeviceMap.forEach((s, d) -> {
+            // Update new value to local data
+            mLocalDataManager.updateAmbientControlExpanded(d, expanded);
+        });
+    }
+
+    private boolean isControlMuted() {
+        return mPreference != null && mPreference.isMuted();
+    }
+
+    private void initLocalDataIfNeeded() {
+        int smallerVolumeAmongGroup = Integer.MAX_VALUE;
+        for (BluetoothDevice device : mSideToDeviceMap.values()) {
+            Data data = mLocalDataManager.get(device);
+            if (data.ambient() != INVALID_VOLUME) {
+                smallerVolumeAmongGroup = Math.min(data.ambient(), smallerVolumeAmongGroup);
+            } else if (data.groupAmbient() != INVALID_VOLUME) {
+                // Initialize side ambient from group ambient value
+                mLocalDataManager.updateAmbient(device, data.groupAmbient());
+            }
+        }
+        if (smallerVolumeAmongGroup != Integer.MAX_VALUE) {
+            for (BluetoothDevice device : mSideToDeviceMap.values()) {
+                Data data = mLocalDataManager.get(device);
+                if (data.groupAmbient() == INVALID_VOLUME) {
+                    // Initialize group ambient from smaller side ambient value
+                    mLocalDataManager.updateGroupAmbient(device, smallerVolumeAmongGroup);
+                }
+            }
+        }
+    }
+
+    private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
+            @Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
+        if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
+            if (state.isMuted() != muted) {
+                mVolumeController.setMuted(device, muted);
+            }
+        }
+    }
+
+    private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
+        return device != null && device.isConnected()
+                && mBluetoothManager.getProfileManager().getVolumeControlProfile()
+                .getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
+    }
+
+    private void showErrorToast() {
+        if (mToast != null) {
+            mToast.cancel();
+        }
+        mToast = Toast.makeText(mContext, R.string.bluetooth_ambient_volume_error,
+                Toast.LENGTH_SHORT);
+        mToast.show();
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
index 3703b71..8af0879 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
@@ -42,6 +42,7 @@
 
     public static final int ORDER_HEARING_DEVICE_SETTINGS = 1;
     public static final int ORDER_HEARING_AIDS_PRESETS = 2;
+    public static final int ORDER_AMBIENT_VOLUME = 4;
     static final String KEY_HEARING_DEVICE_GROUP = "hearing_device_group";
 
     private final List<BluetoothDetailsController> mControllers = new ArrayList<>();
@@ -107,6 +108,10 @@
             mControllers.add(new BluetoothDetailsHearingAidsPresetsController(mContext, mFragment,
                     mManager, mCachedDevice, mLifecycle));
         }
+        if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
+            mControllers.add(new BluetoothDetailsAmbientVolumePreferenceController(mContext,
+                    mManager, mFragment, mCachedDevice, mLifecycle));
+        }
     }
 
     @NonNull
diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
index 76abc03..9cac772 100644
--- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
+++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
@@ -16,14 +16,32 @@
 
 package com.android.settings.connecteddevice.display
 
+import android.app.WallpaperManager
 import com.android.settings.R
 
 import android.content.Context
+import android.graphics.Color
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.ColorDrawable
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayTopology
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
+import android.util.Log
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.widget.Button
+import android.widget.FrameLayout
+import android.widget.TextView
 
+import androidx.annotation.VisibleForTesting
 import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
 
 import java.util.Locale
 
@@ -143,11 +161,27 @@
 
 const val PREFERENCE_KEY = "display_topology_preference"
 
+/** dp of padding on each side of a display block. */
+const val BLOCK_PADDING = 2
+
 /**
  * DisplayTopologyPreference allows the user to change the display topology
  * when there is one or more extended display attached.
  */
-class DisplayTopologyPreference(context : Context) : Preference(context) {
+class DisplayTopologyPreference(context : Context)
+        : Preference(context), ViewTreeObserver.OnGlobalLayoutListener {
+    @VisibleForTesting lateinit var mPaneContent : FrameLayout
+    @VisibleForTesting lateinit var mPaneHolder : FrameLayout
+    @VisibleForTesting lateinit var mTopologyHint : TextView
+
+    @VisibleForTesting var injector : Injector
+
+    /**
+     * This is needed to prevent a repopulation of the pane causing another
+     * relayout and vice-versa ad infinitum.
+     */
+    private var mPaneNeedsRefresh = false
+
     init {
         layoutResource = R.layout.display_topology_preference
 
@@ -155,5 +189,108 @@
         isSelectable = false
 
         key = PREFERENCE_KEY
+
+        injector = Injector()
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+
+        val newPane = holder.findViewById(R.id.display_topology_pane_content) as FrameLayout
+        if (this::mPaneContent.isInitialized) {
+            if (newPane == mPaneContent) {
+                return
+            }
+            mPaneContent.viewTreeObserver.removeOnGlobalLayoutListener(this)
+        }
+        mPaneContent = newPane
+        mPaneHolder = holder.itemView as FrameLayout
+        mTopologyHint = holder.findViewById(R.id.topology_hint) as TextView
+        mPaneContent.viewTreeObserver.addOnGlobalLayoutListener(this)
+    }
+
+    override fun onAttached() {
+        // We don't know if topology changes happened when we were detached, as it is impossible to
+        // listen at that time (we must remove listeners when detaching). Setting this flag makes
+        // the following onGlobalLayout call refresh the pane.
+        mPaneNeedsRefresh = true
+    }
+
+    override fun onGlobalLayout() {
+        if (mPaneNeedsRefresh) {
+            mPaneNeedsRefresh = false
+            refreshPane()
+        }
+    }
+
+    open class Injector {
+        open fun displayTopology(context : Context) : DisplayTopology? {
+            val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+            return displayManager.displayTopology
+        }
+
+        open fun wallpaper(context : Context) : Drawable {
+            return WallpaperManager.getInstance(context).drawable ?: ColorDrawable(Color.BLACK)
+        }
+    }
+
+    private fun calcAbsRects(
+            dest : MutableMap<Int, RectF>, n : DisplayTopology.TreeNode, x : Float, y : Float) {
+        dest.put(n.displayId, RectF(x, y, x + n.width, y + n.height))
+
+        for (c in n.children) {
+            val (xoff, yoff) = when (c.position) {
+                POSITION_LEFT -> Pair(-c.width, +c.offset)
+                POSITION_RIGHT -> Pair(+n.width, +c.offset)
+                POSITION_TOP -> Pair(+c.offset, -c.height)
+                POSITION_BOTTOM -> Pair(+c.offset, +n.height)
+                else -> throw IllegalStateException("invalid position for display: ${c}")
+            }
+            calcAbsRects(dest, c, x + xoff, y + yoff)
+        }
+    }
+
+    private fun refreshPane() {
+        mPaneContent.removeAllViews()
+
+        val root = injector.displayTopology(context)?.root
+        if (root == null) {
+            // This occurs when no topology is active.
+            // TODO(b/352648432): show main display or mirrored displays rather than an empty pane.
+            mTopologyHint.text = ""
+            return
+        }
+        mTopologyHint.text = context.getString(R.string.external_display_topology_hint)
+
+        val blocksPos = buildMap { calcAbsRects(this, root, x = 0f, y = 0f) }
+
+        val scaling = TopologyScale(
+                mPaneContent.width, minEdgeLength = 60, maxBlockRatio = 0.12f, blocksPos.values)
+        mPaneHolder.layoutParams.let {
+            if (it.height != scaling.paneHeight) {
+                it.height = scaling.paneHeight
+                mPaneHolder.layoutParams = it
+            }
+        }
+        val wallpaper = injector.wallpaper(context)
+        blocksPos.values.forEach { p ->
+            Button(context).apply {
+                isScrollContainer = false
+                isVerticalScrollBarEnabled = false
+                isHorizontalScrollBarEnabled = false
+                background = wallpaper
+                val topLeft = scaling.displayToPaneCoor(PointF(p.left, p.top))
+                val bottomRight = scaling.displayToPaneCoor(PointF(p.right, p.bottom))
+
+                mPaneContent.addView(this)
+
+                val layout = layoutParams
+                layout.width = bottomRight.x - topLeft.x - BLOCK_PADDING * 2
+                layout.height = bottomRight.y - topLeft.y - BLOCK_PADDING * 2
+                layoutParams = layout
+                x = (topLeft.x + BLOCK_PADDING).toFloat()
+                y = (topLeft.y + BLOCK_PADDING).toFloat()
+            }
+        }
     }
 }
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index 8480c5c..d8e238c 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -18,6 +18,7 @@
 
 import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid;
 import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUidList;
+import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings;
 
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
@@ -45,13 +46,14 @@
 import com.android.settings.datausage.lib.NetworkTemplates;
 import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
 import com.android.settings.network.SubscriptionUtil;
-import com.android.settings.widget.EntityHeaderController;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.AppItem;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.net.UidDetail;
 import com.android.settingslib.net.UidDetailProvider;
+import com.android.settingslib.widget.IntroPreference;
 
 import kotlin.Unit;
 
@@ -65,6 +67,8 @@
     private static final String TAG = "AppDataUsage";
 
     static final String ARG_APP_ITEM = "app_item";
+    @VisibleForTesting
+    static final String ARG_APP_HEADER = "app_header";
     static final String ARG_NETWORK_TEMPLATE = "network_template";
     static final String ARG_NETWORK_CYCLES = "network_cycles";
     static final String ARG_SELECTED_CYCLE = "selected_cycle";
@@ -176,7 +180,7 @@
             removePreference(KEY_RESTRICT_BACKGROUND);
         }
 
-        addEntityHeader();
+        setupIntroPreference();
     }
 
     @Override
@@ -320,32 +324,32 @@
     }
 
     @VisibleForTesting
-    void addEntityHeader() {
-        String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null;
-        int uid = 0;
-        if (pkg != null) {
+    void setupIntroPreference() {
+        final Preference pref = getPreferenceScreen().findPreference(ARG_APP_HEADER);
+        if (pref != null) {
+            pref.setIcon(mIcon);
+            pref.setTitle(mLabel);
+            pref.setSelectable(true);
+        }
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(Preference preference) {
+        if (!(preference instanceof IntroPreference)) return false;
+
+        String pkg = !mPackages.isEmpty() ? mPackages.valueAt(0) : null;
+        if (mAppItem.key > 0 && pkg != null) {
             try {
-                uid = mPackageManager.getPackageUidAsUser(pkg,
+                int uid = mPackageManager.getPackageUidAsUser(pkg,
                         UserHandle.getUserId(mAppItem.key));
+                startAppInfoSettings(pkg, uid, this, 0 /* request */,
+                        FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+                                .getMetricsCategory(this));
             } catch (PackageManager.NameNotFoundException e) {
                 Log.w(TAG, "Skipping UID because cannot find package " + pkg);
             }
         }
-
-        final boolean showInfoButton = mAppItem.key > 0;
-
-        final Activity activity = getActivity();
-        final Preference pref = EntityHeaderController
-                .newInstance(activity, this, null /* header */)
-                .setUid(uid)
-                .setHasAppInfoLink(showInfoButton)
-                .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
-                        EntityHeaderController.ActionType.ACTION_NONE)
-                .setIcon(mIcon)
-                .setLabel(mLabel)
-                .setPackageName(pkg)
-                .done(getPrefContext());
-        getPreferenceScreen().addPreference(pref);
+        return true;
     }
 
     @Override
diff --git a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
index 233e107..fb7101d 100644
--- a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
+++ b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
@@ -17,7 +17,6 @@
 package com.android.settings.datausage
 
 import android.content.Context
-import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.res.stringResource
@@ -28,6 +27,7 @@
 import com.android.settings.spa.preference.ComposePreferenceController
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.Category
 import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.map
@@ -60,7 +60,7 @@
 
     @Composable
     override fun Content() {
-        Column {
+        Category {
             val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
             val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
             val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java
index febdead..a1b0f90 100644
--- a/src/com/android/settings/datausage/SpinnerPreference.java
+++ b/src/com/android/settings/datausage/SpinnerPreference.java
@@ -25,8 +25,10 @@
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
+import com.android.settingslib.widget.GroupSectionDividerMixin;
 
-public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
+public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface,
+        GroupSectionDividerMixin {
 
     private CycleAdapter mAdapter;
     @Nullable
diff --git a/src/com/android/settings/datetime/AutoTimeFormatPreferenceController.java b/src/com/android/settings/datetime/AutoTimeFormatPreferenceController.java
deleted file mode 100644
index 44e7cc6..0000000
--- a/src/com/android/settings/datetime/AutoTimeFormatPreferenceController.java
+++ /dev/null
@@ -1,73 +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.datetime;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.provider.Settings.System;
-import android.text.format.DateFormat;
-
-import com.android.settings.R;
-import com.android.settings.core.TogglePreferenceController;
-
-import java.util.Locale;
-
-public class AutoTimeFormatPreferenceController extends TogglePreferenceController {
-
-    public AutoTimeFormatPreferenceController(Context context, String preferenceKey) {
-        super(context, preferenceKey);
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        return AVAILABLE;
-    }
-
-    @Override
-    public boolean isChecked() {
-        return isAutoTimeFormatSelection(mContext);
-    }
-
-    @Override
-    public boolean setChecked(boolean isChecked) {
-        Boolean is24Hour;
-        if (isChecked) {
-            is24Hour = null;
-        } else {
-            is24Hour = is24HourLocale(mContext.getResources().getConfiguration().locale);
-        }
-        TimeFormatPreferenceController.update24HourFormat(mContext, is24Hour);
-        return true;
-    }
-
-    @Override
-    public int getSliceHighlightMenuRes() {
-        return R.string.menu_key_system;
-    }
-
-    boolean is24HourLocale(Locale locale) {
-        return DateFormat.is24HourLocale(locale);
-    }
-
-    /**
-     * Returns if the system is currently configured to pick the time format automatically based on
-     * the locale.
-     */
-    static boolean isAutoTimeFormatSelection(Context context) {
-        return Settings.System.getString(context.getContentResolver(), System.TIME_12_24) == null;
-    }
-}
diff --git a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
index 3d8f801..ecd416d 100644
--- a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
+++ b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
@@ -32,7 +32,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.flags.Flags;
 
 public class AutoTimeZonePreferenceController extends TogglePreferenceController {
 
@@ -107,19 +106,17 @@
         TimeZoneConfiguration.Builder configuration = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(isChecked);
 
-        if (Flags.revampToggles()) {
-            // "Use location for time zone" is only used if "Automatic time zone" is enabled. If
-            // the user toggles off automatic time zone, set the toggle off and disable the toggle.
-            int geoDetectionCapability = mTimeManager
-                    .getTimeZoneCapabilitiesAndConfig()
-                    .getCapabilities()
-                    .getConfigureGeoDetectionEnabledCapability();
+        // "Use location for time zone" is only used if "Automatic time zone" is enabled. If
+        // the user toggles off automatic time zone, set the toggle off and disable the toggle.
+        int geoDetectionCapability = mTimeManager
+                .getTimeZoneCapabilitiesAndConfig()
+                .getCapabilities()
+                .getConfigureGeoDetectionEnabledCapability();
 
-            if (!isChecked
-                    && (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE
-                    || geoDetectionCapability == CAPABILITY_POSSESSED)) {
-                configuration.setGeoDetectionEnabled(false);
-            }
+        if (!isChecked
+                && (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE
+                || geoDetectionCapability == CAPABILITY_POSSESSED)) {
+            configuration.setGeoDetectionEnabled(false);
         }
 
         boolean result = mTimeManager.updateTimeZoneConfiguration(configuration.build());
diff --git a/src/com/android/settings/datetime/DateTimeSettings.java b/src/com/android/settings/datetime/DateTimeSettings.java
index e5c13bf..f3c11d4 100644
--- a/src/com/android/settings/datetime/DateTimeSettings.java
+++ b/src/com/android/settings/datetime/DateTimeSettings.java
@@ -23,7 +23,6 @@
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
 
@@ -50,9 +49,6 @@
 
     @Override
     protected int getPreferenceScreenResId() {
-        if (Flags.revampToggles()) {
-            return R.xml.date_time_prefs_revamped;
-        }
         return R.xml.date_time_prefs;
     }
 
@@ -123,6 +119,5 @@
     }
 
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider(
-                    Flags.revampToggles() ? R.xml.date_time_prefs_revamped : R.xml.date_time_prefs);
+            new BaseSearchIndexProvider(R.xml.date_time_prefs);
 }
diff --git a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java
index 52d49ac..d475d9d 100644
--- a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java
+++ b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java
@@ -32,7 +32,6 @@
 import com.android.settings.R;
 import com.android.settings.core.InstrumentedPreferenceFragment;
 import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.flags.Flags;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -68,7 +67,7 @@
         // forceRefresh set to true as the location toggle may have been turned off by switching off
         // automatic time zone
         TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ Flags.revampToggles());
+                getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ true);
         TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
         return configuration.isGeoDetectionEnabled();
     }
@@ -137,11 +136,7 @@
         if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) {
             return UNSUPPORTED_ON_DEVICE;
         } else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) {
-            if (Flags.revampToggles()) {
-                return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
-            } else {
-                return AVAILABLE;
-            }
+            return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
         } else {
             throw new IllegalStateException("Unknown capability=" + capability);
         }
@@ -151,10 +146,8 @@
     public void updateState(Preference preference) {
         super.updateState(preference);
 
-        if (Flags.revampToggles()) {
-            // enable / disable the toggle based on automatic time zone being enabled or not
-            preference.setEnabled(isAutoTimeZoneEnabled());
-        }
+        // enable / disable the toggle based on automatic time zone being enabled or not
+        preference.setEnabled(isAutoTimeZoneEnabled());
     }
 
     @Override
diff --git a/src/com/android/settings/datetime/TimeFormatPreferenceController.java b/src/com/android/settings/datetime/TimeFormatPreferenceController.java
index 2dee76e..c400f4e 100644
--- a/src/com/android/settings/datetime/TimeFormatPreferenceController.java
+++ b/src/com/android/settings/datetime/TimeFormatPreferenceController.java
@@ -25,7 +25,6 @@
 
 import com.android.settings.R;
 import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.flags.Flags;
 
 import java.util.Calendar;
 import java.util.Date;
@@ -73,11 +72,6 @@
         if (mIsFromSUW) {
             return DISABLED_DEPENDENT_SETTING;
         }
-        if (!Flags.revampToggles()) {
-            if (AutoTimeFormatPreferenceController.isAutoTimeFormatSelection(mContext)) {
-                return DISABLED_DEPENDENT_SETTING;
-            }
-        }
         return AVAILABLE;
     }
 
@@ -120,12 +114,12 @@
         return DateFormat.is24HourFormat(mContext);
     }
 
-    static void update24HourFormat(Context context, Boolean is24Hour) {
+    private static void update24HourFormat(Context context, Boolean is24Hour) {
         set24Hour(context, is24Hour);
         timeUpdated(context, is24Hour);
     }
 
-    static void timeUpdated(Context context, Boolean is24Hour) {
+    private static void timeUpdated(Context context, Boolean is24Hour) {
         Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
         timeChanged.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         int timeFormatPreference;
@@ -139,9 +133,8 @@
         context.sendBroadcast(timeChanged);
     }
 
-    static void set24Hour(Context context, Boolean is24Hour) {
-        String value = is24Hour == null ? null :
-                is24Hour ? HOURS_24 : HOURS_12;
+    private static void set24Hour(Context context, boolean is24Hour) {
+        String value = is24Hour ? HOURS_24 : HOURS_12;
         Settings.System.putString(context.getContentResolver(),
                 Settings.System.TIME_12_24, value);
     }
diff --git a/src/com/android/settings/display/BrightnessLevelPreference.kt b/src/com/android/settings/display/BrightnessLevelPreference.kt
index 5e2f6f2..73a2280 100644
--- a/src/com/android/settings/display/BrightnessLevelPreference.kt
+++ b/src/com/android/settings/display/BrightnessLevelPreference.kt
@@ -31,16 +31,20 @@
 import com.android.settings.Utils
 import com.android.settings.core.SettingsBaseActivity
 import com.android.settingslib.RestrictedPreference
+import com.android.settingslib.datastore.AbstractKeyedDataObservable
+import com.android.settingslib.datastore.DataChangeReason
 import com.android.settingslib.datastore.HandlerExecutor
+import com.android.settingslib.datastore.KeyValueStore
 import com.android.settingslib.datastore.KeyedObserver
 import com.android.settingslib.datastore.SettingsSystemStore
 import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MAX
 import com.android.settingslib.display.BrightnessUtils.GAMMA_SPACE_MIN
 import com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat
-import com.android.settingslib.metadata.PreferenceLifecycleContext
-import com.android.settingslib.metadata.PreferenceLifecycleProvider
+import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceSummaryProvider
+import com.android.settingslib.metadata.ReadWritePermit
+import com.android.settingslib.metadata.SensitivityLevel
 import com.android.settingslib.preference.PreferenceBinding
 import com.android.settingslib.transition.SettingsTransitionHelper
 import java.text.NumberFormat
@@ -48,15 +52,12 @@
 // LINT.IfChange
 class BrightnessLevelPreference :
     PreferenceMetadata,
+    PersistentPreference<Float>,
     PreferenceBinding,
     PreferenceRestrictionMixin,
     PreferenceSummaryProvider,
-    PreferenceLifecycleProvider,
     Preference.OnPreferenceClickListener {
 
-    private var brightnessObserver: KeyedObserver<String>? = null
-    private var displayListener: DisplayListener? = null
-
     override val key: String
         get() = KEY
 
@@ -67,7 +68,7 @@
         get() = R.string.keywords_display_brightness_level
 
     override fun getSummary(context: Context): CharSequence? =
-        NumberFormat.getPercentInstance().format(getCurrentBrightness(context))
+        NumberFormat.getPercentInstance().format(context.brightness)
 
     override fun isEnabled(context: Context) = super<PreferenceRestrictionMixin>.isEnabled(context)
 
@@ -77,91 +78,113 @@
     override val useAdminDisabledSummary: Boolean
         get() = true
 
+    override fun intent(context: Context) =
+        Intent(ACTION_SHOW_BRIGHTNESS_DIALOG)
+            .setPackage(Utils.SYSTEMUI_PACKAGE_NAME)
+            .putExtra(
+                SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
+                SettingsTransitionHelper.TransitionType.TRANSITION_NONE,
+            )
+            .putExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, true)
+
     override fun createWidget(context: Context) = RestrictedPreference(context)
 
     override fun bind(preference: Preference, metadata: PreferenceMetadata) {
         super.bind(preference, metadata)
         preference.onPreferenceClickListener = this
+        preference.isPersistent = false
     }
 
-    override fun onStart(context: PreferenceLifecycleContext) {
-        val observer = KeyedObserver<String> { _, _ -> context.notifyPreferenceChange(KEY) }
-        brightnessObserver = observer
-        SettingsSystemStore.get(context)
-            .addObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, observer, HandlerExecutor.main)
+    override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
+        ReadWritePermit.ALLOW
 
-        val listener =
-            object : DisplayListener {
-                override fun onDisplayAdded(displayId: Int) {}
+    override fun getWritePermit(context: Context, value: Float?, callingPid: Int, callingUid: Int) =
+        ReadWritePermit.DISALLOW
 
-                override fun onDisplayRemoved(displayId: Int) {}
+    override val sensitivityLevel
+        get() = SensitivityLevel.NO_SENSITIVITY
 
-                override fun onDisplayChanged(displayId: Int) {
-                    context.notifyPreferenceChange(KEY)
-                }
-            }
-        displayListener = listener
-        context.displayManager.registerDisplayListener(
-            listener,
-            HandlerExecutor.main,
-            /* eventFlags= */ 0,
-            DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
-        )
-    }
+    override fun storage(context: Context): KeyValueStore = BrightnessStorage(context)
 
-    override fun onStop(context: PreferenceLifecycleContext) {
-        brightnessObserver?.let {
-            SettingsSystemStore.get(context).removeObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, it)
-            brightnessObserver = null
+    private class BrightnessStorage(private val context: Context) :
+        AbstractKeyedDataObservable<String>(),
+        KeyValueStore,
+        KeyedObserver<String>,
+        DisplayListener {
+
+        override fun contains(key: String) = key == KEY
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T : Any> getValue(key: String, valueType: Class<T>) =
+            context.brightness.toFloat() as T
+
+        override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {}
+
+        override fun onFirstObserverAdded() {
+            SettingsSystemStore.get(context)
+                .addObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, this, HandlerExecutor.main)
+
+            context.displayManager.registerDisplayListener(
+                this,
+                HandlerExecutor.main,
+                /* eventFlags= */ 0,
+                DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
+            )
         }
 
-        displayListener?.let {
-            context.displayManager.unregisterDisplayListener(it)
-            displayListener = null
+        override fun onLastObserverRemoved() {
+            SettingsSystemStore.get(context).removeObserver(System.SCREEN_AUTO_BRIGHTNESS_ADJ, this)
+
+            context.displayManager.unregisterDisplayListener(this)
+        }
+
+        override fun onKeyChanged(key: String, reason: Int) {
+            notifyChange(KEY, DataChangeReason.UPDATE)
+        }
+
+        override fun onDisplayAdded(displayId: Int) {}
+
+        override fun onDisplayRemoved(displayId: Int) {}
+
+        override fun onDisplayChanged(displayId: Int) {
+            notifyChange(KEY, DataChangeReason.UPDATE)
         }
     }
 
-    private val Context.displayManager: DisplayManager
-        get() = getSystemService(DisplayManager::class.java)!!
-
     override fun onPreferenceClick(preference: Preference): Boolean {
         val context = preference.context
-        val intent =
-            Intent(ACTION_SHOW_BRIGHTNESS_DIALOG)
-                .setPackage(Utils.SYSTEMUI_PACKAGE_NAME)
-                .putExtra(
-                    SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
-                    SettingsTransitionHelper.TransitionType.TRANSITION_NONE,
-                )
-                .putExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, true)
         val options =
             ActivityOptions.makeCustomAnimation(
                 context,
                 android.R.anim.fade_in,
                 android.R.anim.fade_out,
             )
-        context.startActivityForResult(preference.key, intent, 0, options.toBundle())
+        context.startActivityForResult(preference.key, intent(context), 0, options.toBundle())
         return true
     }
 
-    private fun getCurrentBrightness(context: Context): Double {
-        val info: BrightnessInfo? = context.display.brightnessInfo
-        val value =
-            info?.run {
-                convertLinearToGammaFloat(brightness, brightnessMinimum, brightnessMaximum)
-            }
-        return getPercentage(value?.toDouble() ?: 0.0)
-    }
-
-    private fun getPercentage(value: Double): Double =
-        when {
-            value > GAMMA_SPACE_MAX -> 1.0
-            value < GAMMA_SPACE_MIN -> 0.0
-            else -> (value - GAMMA_SPACE_MIN) / (GAMMA_SPACE_MAX - GAMMA_SPACE_MIN)
-        }
-
     companion object {
         const val KEY = "brightness"
+
+        private val Context.displayManager: DisplayManager
+            get() = getSystemService(DisplayManager::class.java)!!
+
+        private val Context.brightness: Double
+            get() {
+                val info: BrightnessInfo? = display.brightnessInfo
+                val value =
+                    info?.run {
+                        convertLinearToGammaFloat(brightness, brightnessMinimum, brightnessMaximum)
+                    }
+                return getPercentage(value?.toDouble() ?: 0.0)
+            }
+
+        private fun getPercentage(value: Double): Double =
+            when {
+                value > GAMMA_SPACE_MAX -> 1.0
+                value < GAMMA_SPACE_MIN -> 0.0
+                else -> (value - GAMMA_SPACE_MIN) / (GAMMA_SPACE_MAX - GAMMA_SPACE_MIN)
+            }
     }
 }
 // LINT.ThenChange(BrightnessLevelPreferenceController.java)
diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java
index 0378888..0a2599e 100644
--- a/src/com/android/settings/gestures/OneHandedSettings.java
+++ b/src/com/android/settings/gestures/OneHandedSettings.java
@@ -22,9 +22,9 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
+import android.widget.CompoundButton;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -33,7 +33,6 @@
 import com.android.settings.R;
 import com.android.settings.accessibility.AccessibilityFragmentUtils;
 import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
-import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.search.SearchIndexableRaw;
@@ -82,12 +81,7 @@
 
         final MainSwitchPreference mainSwitchPreference =
                 getPreferenceScreen().findPreference(ONE_HANDED_MAIN_SWITCH_KEY);
-        mainSwitchPreference.addOnSwitchChangeListener((switchView, isChecked) -> {
-            switchView.setChecked(isChecked);
-            if (isChecked) {
-                showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
-            }
-        });
+        mainSwitchPreference.addOnSwitchChangeListener(CompoundButton::setChecked);
     }
 
     @Override
@@ -146,24 +140,6 @@
     }
 
     @Override
-    protected ComponentName getTileComponentName() {
-        return AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME;
-    }
-
-    @Override
-    protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) {
-        final Context context = getContext();
-        if (context == null) {
-            Log.w(TAG, "OneHandedSettings not attached to a context.");
-            return null;
-        }
-        return type == QuickSettingsTooltipType.GUIDE_TO_EDIT
-                ? context.getText(R.string.accessibility_one_handed_mode_qs_tooltip_content)
-                : context.getText(
-                        R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content);
-    }
-
-    @Override
     protected int getPreferenceScreenResId() {
         return R.xml.one_handed_settings;
     }
diff --git a/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java b/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java
index 74c156c..c0b65df 100644
--- a/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java
+++ b/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java
@@ -22,9 +22,12 @@
 import android.content.pm.PackageManager;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.internal.annotations.Initializer;
 import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
 import com.android.settingslib.applications.DefaultAppInfo;
 import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -43,7 +46,8 @@
     private Preference mPreference;
     private Context mContext;
 
-    public DefaultVoiceInputPreferenceController(Context context, Lifecycle lifecycle) {
+    public DefaultVoiceInputPreferenceController(
+            @NonNull Context context, @Nullable Lifecycle lifecycle) {
         super(context);
         mContext = context;
         mHelper = new VoiceInputHelper(context);
@@ -65,6 +69,7 @@
     }
 
     @Override
+    @Initializer
     public void displayPreference(PreferenceScreen screen) {
         mScreen = screen;
         mPreference = screen.findPreference(getPreferenceKey());
diff --git a/src/com/android/settings/language/LanguageAndRegionPreferenceController.java b/src/com/android/settings/language/LanguageAndRegionPreferenceController.java
new file mode 100644
index 0000000..4e554ed
--- /dev/null
+++ b/src/com/android/settings/language/LanguageAndRegionPreferenceController.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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.language;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
+
+/**
+ * This is a display controller for new language activity entry.
+ * TODO(b/379962955): When new layout is on board, all old layouts should be removed.
+ */
+public class LanguageAndRegionPreferenceController extends BasePreferenceController {
+
+    public LanguageAndRegionPreferenceController(@NonNull Context context, @NonNull String key) {
+        super(context, key);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (!Flags.regionalPreferencesApiEnabled()) {
+            return CONDITIONALLY_UNAVAILABLE;
+        }
+        // TODO: Add setComponentEnabledSetting after LanguageAndRegionSettingsActivity is created.
+        return AVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/language/LanguageAndRegionSettings.java b/src/com/android/settings/language/LanguageAndRegionSettings.java
new file mode 100644
index 0000000..a405878
--- /dev/null
+++ b/src/com/android/settings/language/LanguageAndRegionSettings.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 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.language;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.PreferenceCategoryController;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SearchIndexable
+public class LanguageAndRegionSettings extends DashboardFragment {
+
+    private static final String KEY_SPEECH_CATEGORY = "speech_category";
+    private static final String KEY_ON_DEVICE_RECOGNITION = "on_device_recognition_settings";
+    private static final String KEY_TEXT_TO_SPEECH = "tts_settings_summary";
+
+    private static final String TAG = "LanguageAndRegionSettings";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.SETTINGS_LANGUAGES_CATEGORY;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // Hack to update action bar title. It's necessary to refresh title because this page user
+        // can change locale from here and fragment won't relaunch. Once language changes, title
+        // must display in the new language.
+        final Activity activity = getActivity();
+        if (activity == null) {
+            return;
+        }
+        activity.setTitle(R.string.languages_settings);
+    }
+
+    @Override
+    public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
+        return LanguageSettingScreen.KEY;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.language_and_region_settings;
+    }
+
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        return buildPreferenceControllers(context, getSettingsLifecycle());
+    }
+
+    private static List<AbstractPreferenceController> buildPreferenceControllers(
+            @NonNull Context context, @Nullable Lifecycle lifecycle) {
+        final List<AbstractPreferenceController> controllers = new ArrayList<>();
+
+        final DefaultVoiceInputPreferenceController defaultVoiceInputPreferenceController =
+                new DefaultVoiceInputPreferenceController(context, lifecycle);
+        final TtsPreferenceController ttsPreferenceController =
+                new TtsPreferenceController(context, KEY_TEXT_TO_SPEECH);
+        final OnDeviceRecognitionPreferenceController onDeviceRecognitionPreferenceController =
+                new OnDeviceRecognitionPreferenceController(context, KEY_ON_DEVICE_RECOGNITION);
+
+        controllers.add(defaultVoiceInputPreferenceController);
+        controllers.add(ttsPreferenceController);
+        List<AbstractPreferenceController> speechCategoryChildren = new ArrayList<>(
+                List.of(defaultVoiceInputPreferenceController, ttsPreferenceController));
+
+        if (onDeviceRecognitionPreferenceController.isAvailable()) {
+            controllers.add(onDeviceRecognitionPreferenceController);
+            speechCategoryChildren.add(onDeviceRecognitionPreferenceController);
+        }
+
+        controllers.add(new PreferenceCategoryController(context, KEY_SPEECH_CATEGORY)
+                .setChildren(speechCategoryChildren));
+
+        return controllers;
+    }
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.language_and_region_settings) {
+
+                @Override
+                @NonNull
+                public List<AbstractPreferenceController> createPreferenceControllers(
+                        @NonNull Context context) {
+                    return buildPreferenceControllers(context, null);
+                }
+
+                @Override
+                protected boolean isPageSearchEnabled(Context context) {
+                    if (Flags.regionalPreferencesApiEnabled()) {
+                        return true;
+                    }
+                    return false;
+                }
+            };
+}
diff --git a/src/com/android/settings/language/LanguagePreferenceController.java b/src/com/android/settings/language/LanguagePreferenceController.java
index 84624a2..90aaec4 100644
--- a/src/com/android/settings/language/LanguagePreferenceController.java
+++ b/src/com/android/settings/language/LanguagePreferenceController.java
@@ -22,6 +22,7 @@
 
 import com.android.settings.Settings;
 import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
 
 /**
  * This is a display controller for new language activity entry.
@@ -34,6 +35,10 @@
 
     @Override
     public int getAvailabilityStatus() {
+        if (Flags.regionalPreferencesApiEnabled()) {
+            setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, false);
+            return CONDITIONALLY_UNAVAILABLE;
+        }
         setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, true);
         return AVAILABLE;
     }
diff --git a/src/com/android/settings/language/LanguageSettings.java b/src/com/android/settings/language/LanguageSettings.java
index d992ff2..58df053 100644
--- a/src/com/android/settings/language/LanguageSettings.java
+++ b/src/com/android/settings/language/LanguageSettings.java
@@ -25,6 +25,7 @@
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.widget.PreferenceCategoryController;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -116,6 +117,9 @@
                 }
                 @Override
                 protected boolean isPageSearchEnabled(Context context) {
+                    if (Flags.regionalPreferencesApiEnabled()) {
+                        return false;
+                    }
                     return true;
                 }
             };
diff --git a/src/com/android/settings/network/apn/ApnPreference.java b/src/com/android/settings/network/apn/ApnPreference.java
index 55258c1..3ba3508 100644
--- a/src/com/android/settings/network/apn/ApnPreference.java
+++ b/src/com/android/settings/network/apn/ApnPreference.java
@@ -24,31 +24,29 @@
 import android.net.Uri;
 import android.provider.Telephony;
 import android.telephony.SubscriptionManager;
-import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.widget.CompoundButton;
 import android.widget.RadioButton;
-import android.widget.RelativeLayout;
 import android.widget.Toast;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
 import com.android.settings.flags.Flags;
 import com.android.settings.spa.SpaActivity;
+import com.android.settingslib.widget.TwoTargetPreference;
 
 /**
  * Preference of APN UI entry
  */
-public class ApnPreference extends Preference
-        implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
+public class ApnPreference extends TwoTargetPreference
+        implements CompoundButton.OnCheckedChangeListener, Preference.OnPreferenceClickListener {
     private static final String TAG = "ApnPreference";
     private boolean mIsChecked = false;
-    @Nullable
-    private RadioButton mRadioButton = null;
+    private RadioButton mRadioButton;
     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private boolean mProtectFromCheckedChange = false;
     private boolean mDefaultSelectable = true;
@@ -57,50 +55,36 @@
     /**
      * Constructor of Preference
      */
-    public ApnPreference(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        // Primary target and radio button could be selectable, but entire preference itself is not
-        // selectable.
-        setSelectable(false);
-    }
-
-    /**
-     * Constructor of Preference
-     */
-    public ApnPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.apnPreferenceStyle);
-    }
-
-    /**
-     * Constructor of Preference
-     */
     public ApnPreference(Context context) {
-        this(context, null);
+        super(context);
+        setOnPreferenceClickListener(this);
     }
 
     @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        super.onBindViewHolder(view);
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
 
-        final RelativeLayout textArea = (RelativeLayout) view.findViewById(R.id.text_layout);
-        textArea.setOnClickListener(this);
-
-        final View radioButtonFrame = view.itemView.requireViewById(R.id.apn_radio_button_frame);
-        final RadioButton rb = view.itemView.requireViewById(R.id.apn_radiobutton);
-        mRadioButton = rb;
-        if (mDefaultSelectable) {
-            radioButtonFrame.setOnClickListener((v) -> {
-                rb.performClick();
-            });
-            rb.setOnCheckedChangeListener(this);
-
-            mProtectFromCheckedChange = true;
-            rb.setChecked(mIsChecked);
-            mProtectFromCheckedChange = false;
-            radioButtonFrame.setVisibility(View.VISIBLE);
-        } else {
-            radioButtonFrame.setVisibility(View.GONE);
+        final RadioButton rb = (RadioButton) holder.findViewById(android.R.id.checkbox);
+        final View radioButtonFrame = holder.findViewById(android.R.id.widget_frame);
+        if (rb == null || radioButtonFrame == null) {
+            throw new RuntimeException("Failed to load system layout.");
         }
+
+        mRadioButton = rb;
+        radioButtonFrame.setOnClickListener(v -> rb.performClick());
+        rb.setOnCheckedChangeListener(this);
+        setIsChecked(mIsChecked);
+        rb.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    protected boolean shouldHideSecondTarget() {
+        return !mDefaultSelectable;
+    }
+
+    @Override
+    protected int getSecondTargetResId() {
+        return R.layout.preference_widget_radiobutton;
     }
 
     /**
@@ -118,6 +102,7 @@
     /**
      * Change the preference status.
      */
+    @Override
     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
         Log.i(TAG, "ID: " + getKey() + " :" + isChecked);
         if (mProtectFromCheckedChange) {
@@ -130,19 +115,14 @@
     }
 
     @Override
-    public void onClick(View layoutView) {
-        super.onClick();
+    public boolean onPreferenceClick(@NonNull Preference preference) {
         final Context context = getContext();
         final int pos = Integer.parseInt(getKey());
-        if (context == null) {
-            Log.w(TAG, "No context available for pos=" + pos);
-            return;
-        }
 
         if (mHideDetails) {
             Toast.makeText(context, context.getString(R.string.cannot_change_apn_toast),
                     Toast.LENGTH_LONG).show();
-            return;
+            return true;
         }
 
         final Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
@@ -156,6 +136,7 @@
             editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             context.startActivity(editIntent);
         }
+        return true;
     }
 
     public void setDefaultSelectable(boolean defaultSelectable) {
diff --git a/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.java b/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.java
index 9f15cf4..231a34e 100644
--- a/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.java
+++ b/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.java
@@ -63,7 +63,7 @@
             new BaseSearchIndexProvider(R.xml.regional_preferences_measurement_system) {
                 @Override
                 protected boolean isPageSearchEnabled(Context context) {
-                    if (Flags.regionalPreferencesApiEnabled()) {
+                    if (!Flags.regionalPreferencesApiEnabled()) {
                         return false;
                     }
                     return true;
diff --git a/src/com/android/settings/spa/preference/ComposePreference.kt b/src/com/android/settings/spa/preference/ComposePreference.kt
index 91b2d8a..57aa386 100644
--- a/src/com/android/settings/spa/preference/ComposePreference.kt
+++ b/src/com/android/settings/spa/preference/ComposePreference.kt
@@ -28,7 +28,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.widget.GroupSectionDividerMixin
 
-open class ComposeMainSwitchPreference @JvmOverloads constructor(
+open class ComposeGroupSectionPreference @JvmOverloads constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java
index 5973d26..552a4a7 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java
@@ -19,7 +19,6 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -37,7 +36,6 @@
 import android.content.Intent;
 import android.icu.text.CaseMap;
 import android.os.Bundle;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -57,7 +55,6 @@
 import com.android.settings.testutils.shadow.ShadowFragment;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -83,18 +80,11 @@
             PLACEHOLDER_PACKAGE_NAME + "tile.placeholder";
     private static final ComponentName PLACEHOLDER_COMPONENT_NAME = new ComponentName(
             PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CLASS_NAME);
-    private static final ComponentName PLACEHOLDER_TILE_COMPONENT_NAME = new ComponentName(
-            PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_TILE_CLASS_NAME);
-    private static final String PLACEHOLDER_TILE_TOOLTIP_CONTENT =
-            PLACEHOLDER_PACKAGE_NAME + "tooltip_content";
-    private static final String PLACEHOLDER_DIALOG_TITLE = "title";
 
     private static final String SOFTWARE_SHORTCUT_KEY =
             Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
     private static final String HARDWARE_SHORTCUT_KEY =
             Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private TestAccessibilityShortcutPreferenceFragment mFragment;
     private PreferenceScreen mScreen;
     private Context mContext = ApplicationProvider.getApplicationContext();
@@ -153,14 +143,6 @@
 
     @Test
     @Config(shadows = ShadowFragment.class)
-    public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() {
-        mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT);
-
-        assertThat(getLatestPopupWindow()).isNull();
-    }
-
-    @Test
-    @Config(shadows = ShadowFragment.class)
     public void showGeneralCategory_shouldInitCategory() {
         final Bundle savedInstanceState = new Bundle();
         when(mFragment.showGeneralCategory()).thenReturn(true);
@@ -261,16 +243,6 @@
         }
 
         @Override
-        protected ComponentName getTileComponentName() {
-            return PLACEHOLDER_TILE_COMPONENT_NAME;
-        }
-
-        @Override
-        protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) {
-            return PLACEHOLDER_TILE_TOOLTIP_CONTENT;
-        }
-
-        @Override
         public int getUserShortcutTypes() {
             return 0;
         }
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
index 86322f9..6dbb8b5 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
@@ -316,14 +316,6 @@
     }
 
     @Test
-    @Config(shadows = ShadowFragment.class)
-    public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() {
-        mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT);
-
-        assertThat(getLatestPopupWindow()).isNull();
-    }
-
-    @Test
     public void getShortcutTypeSummary_shortcutSummaryIsCorrectlySet() {
         final PreferredShortcut userPreferredShortcut = new PreferredShortcut(
                 PLACEHOLDER_COMPONENT_NAME.flattenToString(),
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java
new file mode 100644
index 0000000..ec406c4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2024 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.android.settings.bluetooth.AmbientVolumePreference.ROTATION_COLLAPSED;
+import static com.android.settings.bluetooth.AmbientVolumePreference.ROTATION_EXPANDED;
+import static com.android.settings.bluetooth.AmbientVolumePreference.SIDE_UNIFIED;
+import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME;
+import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.widget.SeekBarPreference;
+
+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.robolectric.RobolectricTestRunner;
+
+import java.util.Map;
+
+/** Tests for {@link AmbientVolumePreference}. */
+@RunWith(RobolectricTestRunner.class)
+public class AmbientVolumePreferenceTest {
+
+    private static final int TEST_LEFT_VOLUME_LEVEL = 1;
+    private static final int TEST_RIGHT_VOLUME_LEVEL = 2;
+    private static final int TEST_UNIFIED_VOLUME_LEVEL = 3;
+    private static final String KEY_UNIFIED_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_UNIFIED;
+    private static final String KEY_LEFT_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
+    private static final String KEY_RIGHT_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Spy
+    private Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock
+    private AmbientVolumePreference.OnIconClickListener mListener;
+    @Mock
+    private View mItemView;
+
+    private AmbientVolumePreference mPreference;
+    private ImageView mExpandIcon;
+    private ImageView mVolumeIcon;
+    private final Map<Integer, SeekBarPreference> mSideToSlidersMap = new ArrayMap<>();
+
+    @Before
+    public void setUp() {
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
+        mPreference = new AmbientVolumePreference(mContext);
+        mPreference.setKey(KEY_AMBIENT_VOLUME);
+        mPreference.setOnIconClickListener(mListener);
+        mPreference.setExpandable(true);
+        mPreference.setMutable(true);
+        preferenceScreen.addPreference(mPreference);
+
+        prepareSliders();
+        mPreference.setSliders(mSideToSlidersMap);
+
+        mExpandIcon = new ImageView(mContext);
+        mVolumeIcon = new ImageView(mContext);
+        mVolumeIcon.setImageResource(com.android.settingslib.R.drawable.ic_ambient_volume);
+        mVolumeIcon.setImageLevel(0);
+        when(mItemView.requireViewById(R.id.expand_icon)).thenReturn(mExpandIcon);
+        when(mItemView.requireViewById(com.android.internal.R.id.icon)).thenReturn(mVolumeIcon);
+        when(mItemView.requireViewById(R.id.icon_frame)).thenReturn(mVolumeIcon);
+
+        PreferenceViewHolder preferenceViewHolder = PreferenceViewHolder.createInstanceForTests(
+                mItemView);
+        mPreference.onBindViewHolder(preferenceViewHolder);
+    }
+
+    @Test
+    public void setExpandable_expandable_expandIconVisible() {
+        mPreference.setExpandable(true);
+
+        assertThat(mExpandIcon.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void setExpandable_notExpandable_expandIconGone() {
+        mPreference.setExpandable(false);
+
+        assertThat(mExpandIcon.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void setExpanded_expanded_assertControlUiCorrect() {
+        mPreference.setExpanded(true);
+
+        assertControlUiCorrect();
+    }
+
+    @Test
+    public void setExpanded_notExpanded_assertControlUiCorrect() {
+        mPreference.setExpanded(false);
+
+        assertControlUiCorrect();
+    }
+
+    @Test
+    public void setMutable_mutable_clickOnMuteIconChangeMuteState() {
+        mPreference.setMutable(true);
+        mPreference.setMuted(false);
+
+        mVolumeIcon.callOnClick();
+
+        assertThat(mPreference.isMuted()).isTrue();
+    }
+
+    @Test
+    public void setMutable_notMutable_clickOnMuteIconWontChangeMuteState() {
+        mPreference.setMutable(false);
+        mPreference.setMuted(false);
+
+        mVolumeIcon.callOnClick();
+
+        assertThat(mPreference.isMuted()).isFalse();
+    }
+
+    @Test
+    public void updateLayout_mute_volumeIconIsCorrect() {
+        mPreference.setMuted(true);
+        mPreference.updateLayout();
+
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(0);
+    }
+
+    @Test
+    public void updateLayout_unmuteAndExpanded_volumeIconIsCorrect() {
+        mPreference.setMuted(false);
+        mPreference.setExpanded(true);
+        mPreference.updateLayout();
+
+        int expectedLevel = calculateVolumeLevel(TEST_LEFT_VOLUME_LEVEL, TEST_RIGHT_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void updateLayout_unmuteAndNotExpanded_volumeIconIsCorrect() {
+        mPreference.setMuted(false);
+        mPreference.setExpanded(false);
+        mPreference.updateLayout();
+
+        int expectedLevel = calculateVolumeLevel(TEST_UNIFIED_VOLUME_LEVEL,
+                TEST_UNIFIED_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void setSliderEnabled_expandedAndLeftIsDisabled_volumeIconIcCorrect() {
+        mPreference.setExpanded(true);
+        mPreference.setSliderEnabled(SIDE_LEFT, false);
+
+        int expectedLevel = calculateVolumeLevel(0, TEST_RIGHT_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    @Test
+    public void setSliderValue_expandedAndLeftValueChanged_volumeIconIcCorrect() {
+        mPreference.setExpanded(true);
+        mPreference.setSliderValue(SIDE_LEFT, 4);
+
+        int expectedLevel = calculateVolumeLevel(4, TEST_RIGHT_VOLUME_LEVEL);
+        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+    }
+
+    private int calculateVolumeLevel(int left, int right) {
+        return left * 5 + right;
+    }
+
+    private void assertControlUiCorrect() {
+        final boolean expanded = mPreference.isExpanded();
+        assertThat(mSideToSlidersMap.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
+        assertThat(mSideToSlidersMap.get(SIDE_LEFT).isVisible()).isEqualTo(expanded);
+        assertThat(mSideToSlidersMap.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded);
+        final float rotation = expanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED;
+        assertThat(mExpandIcon.getRotation()).isEqualTo(rotation);
+    }
+
+    private void prepareSliders() {
+        prepareSlider(SIDE_UNIFIED);
+        prepareSlider(SIDE_LEFT);
+        prepareSlider(SIDE_RIGHT);
+    }
+
+    private void prepareSlider(int side) {
+        SeekBarPreference slider = new SeekBarPreference(mContext);
+        slider.setMin(0);
+        slider.setMax(4);
+        if (side == SIDE_LEFT) {
+            slider.setKey(KEY_LEFT_SLIDER);
+            slider.setProgress(TEST_LEFT_VOLUME_LEVEL);
+        } else if (side == SIDE_RIGHT) {
+            slider.setKey(KEY_RIGHT_SLIDER);
+            slider.setProgress(TEST_RIGHT_VOLUME_LEVEL);
+        } else {
+            slider.setKey(KEY_UNIFIED_SLIDER);
+            slider.setProgress(TEST_UNIFIED_VOLUME_LEVEL);
+        }
+        mSideToSlidersMap.put(side, slider);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
new file mode 100644
index 0000000..975d3b4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2024 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.AudioInputControl.MUTE_DISABLED;
+import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME;
+import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
+import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+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 static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.testutils.shadow.ShadowThreadUtils;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.bluetooth.AmbientVolumeController;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.bluetooth.VolumeControlProfile;
+
+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.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Tests for {@link BluetoothDetailsAmbientVolumePreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        BluetoothDetailsAmbientVolumePreferenceControllerTest.ShadowGlobal.class,
+        ShadowThreadUtils.class
+})
+public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
+        BluetoothDetailsControllerTestBase {
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final String LEFT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
+    private static final String RIGHT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
+    private static final String TEST_ADDRESS = "00:00:00:00:11";
+    private static final String TEST_MEMBER_ADDRESS = "00:00:00:00:22";
+
+    @Mock
+    private CachedBluetoothDevice mCachedMemberDevice;
+    @Mock
+    private BluetoothDevice mDevice;
+    @Mock
+    private BluetoothDevice mMemberDevice;
+    @Mock
+    private HearingDeviceLocalDataManager mLocalDataManager;
+    @Mock
+    private LocalBluetoothManager mBluetoothManager;
+    @Mock
+    private BluetoothEventManager mEventManager;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private VolumeControlProfile mVolumeControlProfile;
+    @Mock
+    private AmbientVolumeController mVolumeController;
+    @Mock
+    private Handler mTestHandler;
+
+    private BluetoothDetailsAmbientVolumePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        super.setUp();
+
+        mContext = spy(mContext);
+        PreferenceCategory deviceControls = new PreferenceCategory(mContext);
+        deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
+        mScreen.addPreference(deviceControls);
+        mController = spy(
+                new BluetoothDetailsAmbientVolumePreferenceController(mContext, mBluetoothManager,
+                        mFragment, mCachedDevice, mLifecycle, mLocalDataManager,
+                        mVolumeController));
+
+        when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
+        when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+        when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
+        when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mVolumeControlProfile.getConnectionStatus(mMemberDevice)).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+        when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(
+                new HearingDeviceLocalDataManager.Data.Builder().build());
+
+        when(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
+        when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+                invocationOnMock -> {
+                    invocationOnMock.getArgument(0, Runnable.class).run();
+                    return null;
+                });
+    }
+
+    @Test
+    public void init_deviceWithoutMember_controlNotExpandable() {
+        prepareDevice(/* hasMember= */ false);
+
+        mController.init(mScreen);
+
+        AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
+        assertThat(preference).isNotNull();
+        assertThat(preference.isExpandable()).isFalse();
+    }
+
+    @Test
+    public void init_deviceWithMember_controlExpandable() {
+        prepareDevice(/* hasMember= */ true);
+
+        mController.init(mScreen);
+
+        AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
+        assertThat(preference).isNotNull();
+        assertThat(preference.isExpandable()).isTrue();
+    }
+
+    @Test
+    public void onDeviceLocalDataChange_noMemberAndExpanded_uiCorrectAndDataUpdated() {
+        prepareDevice(/* hasMember= */ false);
+        mController.init(mScreen);
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+
+        mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
+        assertThat(preference).isNotNull();
+        assertThat(preference.isExpanded()).isFalse();
+        verifyDeviceDataUpdated(mDevice);
+    }
+
+    @Test
+    public void onDeviceLocalDataChange_noMemberAndCollapsed_uiCorrectAndDataUpdated() {
+        prepareDevice(/* hasMember= */ false);
+        mController.init(mScreen);
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+
+        mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
+        assertThat(preference).isNotNull();
+        assertThat(preference.isExpanded()).isFalse();
+        verifyDeviceDataUpdated(mDevice);
+    }
+
+    @Test
+    public void onDeviceLocalDataChange_hasMemberAndExpanded_uiCorrectAndDataUpdated() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+
+        mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
+        assertThat(preference).isNotNull();
+        assertThat(preference.isExpanded()).isTrue();
+        verifyDeviceDataUpdated(mDevice);
+    }
+
+    @Test
+    public void onDeviceLocalDataChange_hasMemberAndCollapsed_uiCorrectAndDataUpdated() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+
+        mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
+        assertThat(preference).isNotNull();
+        assertThat(preference.isExpanded()).isFalse();
+        verifyDeviceDataUpdated(mDevice);
+    }
+
+    @Test
+    public void onStart_localDataManagerStartAndCallbackRegistered() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+
+        mController.onStart();
+
+        verify(mLocalDataManager, atLeastOnce()).start();
+        verify(mVolumeController).registerCallback(any(Executor.class), eq(mDevice));
+        verify(mVolumeController).registerCallback(any(Executor.class), eq(mMemberDevice));
+        verify(mCachedDevice).registerCallback(any(Executor.class),
+                any(CachedBluetoothDevice.Callback.class));
+        verify(mCachedMemberDevice).registerCallback(any(Executor.class),
+                any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onStop_localDataManagerStopAndCallbackUnregistered() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+
+        mController.onStop();
+
+        verify(mLocalDataManager).stop();
+        verify(mVolumeController).unregisterCallback(mDevice);
+        verify(mVolumeController).unregisterCallback(mMemberDevice);
+        verify(mCachedDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
+        verify(mCachedMemberDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
+    }
+
+    @Test
+    public void onDeviceAttributesChanged_newDevice_newPreference() {
+        prepareDevice(/* hasMember= */ false);
+        mController.init(mScreen);
+
+        // check the right control is null before onDeviceAttributesChanged()
+        SeekBarPreference leftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
+        SeekBarPreference rightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
+        assertThat(leftControl).isNotNull();
+        assertThat(rightControl).isNull();
+
+        prepareDevice(/* hasMember= */ true);
+        mController.onDeviceAttributesChanged();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        // check the right control is created after onDeviceAttributesChanged()
+        SeekBarPreference updatedLeftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
+        SeekBarPreference updatedRightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
+        assertThat(updatedLeftControl).isEqualTo(leftControl);
+        assertThat(updatedRightControl).isNotNull();
+    }
+
+    @Test
+    public void onAmbientChanged_refreshWhenNotInitiateFromUi() {
+        prepareDevice(/* hasMember= */ false);
+        mController.init(mScreen);
+        final int testAmbient = 10;
+        HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+                .ambient(testAmbient)
+                .groupAmbient(testAmbient)
+                .ambientControlExpanded(false)
+                .build();
+        when(mLocalDataManager.get(mDevice)).thenReturn(data);
+        getPreference().setExpanded(true);
+
+        mController.onAmbientChanged(mDevice, testAmbient);
+        verify(mController, never()).refresh();
+
+        final int updatedTestAmbient = 20;
+        mController.onAmbientChanged(mDevice, updatedTestAmbient);
+        verify(mController).refresh();
+    }
+
+    @Test
+    public void onMuteChanged_refreshWhenNotInitiateFromUi() {
+        prepareDevice(/* hasMember= */ false);
+        mController.init(mScreen);
+        final int testMute = MUTE_NOT_MUTED;
+        AmbientVolumeController.RemoteAmbientState state =
+                new AmbientVolumeController.RemoteAmbientState(testMute, 0);
+        when(mVolumeController.refreshAmbientState(mDevice)).thenReturn(state);
+        getPreference().setMuted(false);
+
+        mController.onMuteChanged(mDevice, testMute);
+        verify(mController, never()).refresh();
+
+        final int updatedTestMute = MUTE_MUTED;
+        mController.onMuteChanged(mDevice, updatedTestMute);
+        verify(mController).refresh();
+    }
+
+    @Test
+    public void refresh_leftAndRightDifferentGainSetting_expandControl() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+        prepareRemoteData(mDevice, 10, MUTE_NOT_MUTED);
+        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+        getPreference().setExpanded(false);
+
+        mController.refresh();
+
+        assertThat(getPreference().isExpanded()).isTrue();
+    }
+
+    @Test
+    public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+        prepareRemoteData(mDevice, 10, MUTE_DISABLED);
+        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+        getPreference().setMutable(true);
+        getPreference().setMuted(true);
+
+        mController.refresh();
+
+        assertThat(getPreference().isMutable()).isFalse();
+        assertThat(getPreference().isMuted()).isFalse();
+    }
+
+    @Test
+    public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
+        prepareDevice(/* hasMember= */ true);
+        mController.init(mScreen);
+        prepareRemoteData(mDevice, 10, MUTE_MUTED);
+        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+        getPreference().setMutable(true);
+        getPreference().setMuted(true);
+
+        mController.refresh();
+
+        assertThat(getPreference().isMutable()).isTrue();
+        assertThat(getPreference().isMuted()).isFalse();
+        verify(mVolumeController).setMuted(mDevice, false);
+    }
+
+    private void prepareDevice(boolean hasMember) {
+        when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+        when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
+        when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+        when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
+        when(mDevice.isConnected()).thenReturn(true);
+        if (hasMember) {
+            when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
+            when(mCachedMemberDevice.getDeviceSide()).thenReturn(SIDE_RIGHT);
+            when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
+            when(mCachedMemberDevice.getBondState()).thenReturn(BOND_BONDED);
+            when(mMemberDevice.getAddress()).thenReturn(TEST_MEMBER_ADDRESS);
+            when(mMemberDevice.getAnonymizedAddress()).thenReturn(TEST_MEMBER_ADDRESS);
+            when(mMemberDevice.isConnected()).thenReturn(true);
+        }
+    }
+
+    private void prepareRemoteData(BluetoothDevice device, int gainSetting, int mute) {
+        when(mVolumeController.isAmbientControlAvailable(device)).thenReturn(true);
+        when(mVolumeController.refreshAmbientState(device)).thenReturn(
+                new AmbientVolumeController.RemoteAmbientState(gainSetting, mute));
+    }
+
+    private void verifyDeviceDataUpdated(BluetoothDevice device) {
+        verify(mLocalDataManager, atLeastOnce()).updateAmbient(eq(device), anyInt());
+        verify(mLocalDataManager, atLeastOnce()).updateGroupAmbient(eq(device), anyInt());
+        verify(mLocalDataManager, atLeastOnce()).updateAmbientControlExpanded(eq(device),
+                anyBoolean());
+    }
+
+    private AmbientVolumePreference getPreference() {
+        return mScreen.findPreference(KEY_AMBIENT_VOLUME);
+    }
+
+    @Implements(value = Settings.Global.class)
+    public static class ShadowGlobal extends ShadowSettings.ShadowGlobal {
+        private static final Map<ContentResolver, Map<String, String>> sDataMap = new HashMap<>();
+
+        @Implementation
+        protected static boolean putStringForUser(
+                ContentResolver cr, String name, String value, int userHandle) {
+            get(cr).put(name, value);
+            return true;
+        }
+
+        @Implementation
+        protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
+            return get(cr).get(name);
+        }
+
+        private static Map<String, String> get(ContentResolver cr) {
+            return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java
index 2a50f89..4e3c742 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControllerTest.java
@@ -53,12 +53,12 @@
     @Mock
     private LocalBluetoothProfileManager mProfileManager;
     @Mock
-    private BluetoothDetailsHearingDeviceController mHearingDeviceController;
-    @Mock
     private BluetoothDetailsHearingAidsPresetsController mPresetsController;
     @Mock
     private BluetoothDetailsHearingDeviceSettingsController mHearingDeviceSettingsController;
 
+    private BluetoothDetailsHearingDeviceController mHearingDeviceController;
+
     @Override
     public void setUp() {
         super.setUp();
@@ -126,4 +126,24 @@
         assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
                 c -> c instanceof BluetoothDetailsHearingAidsPresetsController)).isFalse();
     }
+
+    @Test
+    @RequiresFlagsEnabled(
+            com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+    public void initSubControllers_flagEnabled_ambientVolumeControllerExist() {
+        mHearingDeviceController.initSubControllers(false);
+
+        assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
+                c -> c instanceof BluetoothDetailsAmbientVolumePreferenceController)).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsDisabled(
+            com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+    public void initSubControllers_flagDisabled_ambientVolumeControllerNotExist() {
+        mHearingDeviceController.initSubControllers(false);
+
+        assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
+                c -> c instanceof BluetoothDetailsAmbientVolumePreferenceController)).isFalse();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
new file mode 100644
index 0000000..ad633cc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 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.display
+
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.ColorDrawable
+import android.hardware.display.DisplayTopology
+import android.view.View
+import android.widget.FrameLayout
+import androidx.preference.PreferenceViewHolder
+import androidx.test.core.app.ApplicationProvider
+
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DisplayTopologyPreferenceTest {
+    val context = ApplicationProvider.getApplicationContext<Context>()
+    val preference = DisplayTopologyPreference(context)
+    val injector = TestInjector()
+    val rootView = View.inflate(context, preference.layoutResource, /*parent=*/ null)
+    val holder = PreferenceViewHolder.createInstanceForTests(rootView)
+    val wallpaper = ColorDrawable(Color.MAGENTA)
+
+    init {
+        preference.injector = injector
+        injector.systemWallpaper = wallpaper
+        preference.onBindViewHolder(holder)
+    }
+
+    class TestInjector : DisplayTopologyPreference.Injector() {
+        var topology : DisplayTopology? = null
+        var systemWallpaper : Drawable? = null
+
+        override fun displayTopology(context : Context) : DisplayTopology? { return topology }
+
+        override fun wallpaper(context : Context) : Drawable { return systemWallpaper!! }
+    }
+
+    @Test
+    fun disabledTopology() {
+        preference.onAttached()
+        preference.onGlobalLayout()
+
+        assertThat(preference.mPaneContent.childCount).isEqualTo(0)
+        // TODO(b/352648432): update test when we show the main display even when
+        // a topology is not active.
+        assertThat(preference.mTopologyHint.text).isEqualTo("")
+    }
+
+    @Test
+    fun twoDisplaysGenerateBlocks() {
+        val child = DisplayTopology.TreeNode(
+                /* displayId= */ 42, /* width= */ 100f, /* height= */ 80f,
+                POSITION_LEFT, /* offset= */ 42f)
+        val root = DisplayTopology.TreeNode(
+                /* displayId= */ 0, /* width= */ 200f, /* height= */ 160f,
+                POSITION_LEFT, /* offset= */ 0f)
+        root.addChild(child)
+        injector.topology = DisplayTopology(root, /*primaryDisplayId=*/ 0)
+
+        // This layoutParams needs to be non-null for the global layout handler.
+        preference.mPaneHolder.layoutParams = FrameLayout.LayoutParams(
+                /* width= */ 640, /* height= */ 480)
+
+        // Force pane width to have a reasonable value (hundreds of dp) so the TopologyScale is
+        // calculated reasonably.
+        preference.mPaneContent.left = 0
+        preference.mPaneContent.right = 640
+
+        preference.onAttached()
+        preference.onGlobalLayout()
+
+        assertThat(preference.mPaneContent.childCount).isEqualTo(2)
+        val block0 = preference.mPaneContent.getChildAt(0)
+        val block1 = preference.mPaneContent.getChildAt(1)
+
+        // Block of child display is on the left.
+        val (childBlock, rootBlock) = if (block0.x < block1.x)
+                listOf(block0, block1)
+        else
+                listOf(block1, block0)
+
+        // After accounting for padding, child should be half the length of root in each dimension.
+        assertThat(childBlock.layoutParams.width + BLOCK_PADDING)
+                .isEqualTo(rootBlock.layoutParams.width / 2)
+        assertThat(childBlock.layoutParams.height + BLOCK_PADDING)
+                .isEqualTo(rootBlock.layoutParams.height / 2)
+        assertThat(childBlock.y).isGreaterThan(rootBlock.y)
+        assertThat(block0.background).isEqualTo(wallpaper)
+        assertThat(block1.background).isEqualTo(wallpaper)
+        assertThat(rootBlock.x - BLOCK_PADDING * 2)
+                .isEqualTo(childBlock.x + childBlock.layoutParams.width)
+
+        assertThat(preference.mTopologyHint.text)
+                .isEqualTo(context.getString(R.string.external_display_topology_hint))
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
index 4b8c9de..ce33401 100644
--- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
@@ -28,11 +28,13 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 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.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -42,6 +44,7 @@
 import android.os.Process;
 import android.telephony.SubscriptionManager;
 import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
 
 import androidx.fragment.app.FragmentActivity;
 import androidx.preference.PreferenceManager;
@@ -51,22 +54,20 @@
 import com.android.settings.applications.AppInfoBase;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
-import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
 import com.android.settings.testutils.shadow.ShadowFragment;
 import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
-import com.android.settings.widget.EntityHeaderController;
 import com.android.settingslib.AppItem;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.net.UidDetail;
 import com.android.settingslib.net.UidDetailProvider;
+import com.android.settingslib.widget.IntroPreference;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
@@ -79,27 +80,25 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowEntityHeaderController.class, ShadowRestrictedLockUtilsInternal.class})
+@Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
 public class AppDataUsageTest {
 
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private EntityHeaderController mHeaderController;
     @Mock
     private PackageManager mPackageManager;
 
+    private IntroPreference mIntroPreference;
+
     private AppDataUsage mFragment;
 
+    private Context mContext;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        ShadowEntityHeaderController.setUseMock(mHeaderController);
-        when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController);
-    }
-
-    @After
-    public void tearDown() {
-        ShadowEntityHeaderController.reset();
+        mContext = spy(RuntimeEnvironment.application);
+        mIntroPreference = new IntroPreference(mContext);
+        FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_ENABLE_SPA, true);
     }
 
     @Test
@@ -161,6 +160,7 @@
     }
 
     @Test
+    @Config(shadows = ShadowFragment.class)
     public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
         mFragment = spy(new TestFragment());
 
@@ -169,12 +169,20 @@
         doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
         ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class));
 
-        mFragment.addEntityHeader();
+        when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
+                .thenReturn(mIntroPreference);
+        when(mFragment.getContext()).thenReturn(mContext);
+        doNothing().when(mContext).startActivity(any());
 
-        verify(mHeaderController).setHasAppInfoLink(false);
+        mFragment.setupIntroPreference();
+        mFragment.onPreferenceTreeClick(mIntroPreference);
+
+        verify(mFragment, never()).getActivity();
+        verify(mContext, never()).startActivity(any(Intent.class));
     }
 
     @Test
+    @Config(shadows = ShadowFragment.class)
     public void bindAppHeader_workApp_shouldSetWorkAppUid()
             throws PackageManager.NameNotFoundException {
         final int fakeUserId = 100;
@@ -188,19 +196,21 @@
         ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
         ReflectionHelpers.setField(mFragment, "mPackages", packages);
 
-        when(mPackageManager.getPackageUidAsUser(anyString(), anyInt()))
-                .thenReturn(fakeUserId);
-
-        when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController);
-
+        when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(fakeUserId);
         when(mFragment.getPreferenceManager())
                 .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS));
         doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
 
-        mFragment.addEntityHeader();
+        when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
+                .thenReturn(mIntroPreference);
+        when(mFragment.getContext()).thenReturn(mContext);
+        doNothing().when(mContext).startActivity(any());
 
-        verify(mHeaderController).setHasAppInfoLink(true);
-        verify(mHeaderController).setUid(fakeUserId);
+        mFragment.setupIntroPreference();
+        mFragment.onPreferenceTreeClick(mIntroPreference);
+
+        ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(argumentCaptor.capture());
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeFormatPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeFormatPreferenceControllerTest.java
deleted file mode 100644
index f9b566e..0000000
--- a/tests/robotests/src/com/android/settings/datetime/AutoTimeFormatPreferenceControllerTest.java
+++ /dev/null
@@ -1,152 +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.datetime;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-
-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;
-import org.robolectric.shadows.ShadowApplication;
-
-import java.util.List;
-import java.util.Locale;
-
-@RunWith(RobolectricTestRunner.class)
-public class AutoTimeFormatPreferenceControllerTest {
-
-    @Mock
-    private UpdateTimeAndDateCallback mCallback;
-
-    private ShadowApplication mApplication;
-    private Context mContext;
-    private SwitchPreference mPreference;
-    private TestAutoTimeFormatPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mApplication = ShadowApplication.getInstance();
-        mContext = RuntimeEnvironment.application;
-        mController = new TestAutoTimeFormatPreferenceController(mContext, "test_key");
-        mPreference = new SwitchPreference(mContext);
-        mPreference.setKey("test_key");
-    }
-
-    @Test
-    public void updateState_24HourSet_shouldCheckPreference() {
-        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
-                TimeFormatPreferenceController.HOURS_24);
-
-        mController.updateState(mPreference);
-
-        assertThat(mPreference.isChecked()).isFalse();
-    }
-
-    @Test
-    public void updateState_12HourSet_shouldCheckPreference() {
-        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
-                TimeFormatPreferenceController.HOURS_12);
-
-        mController.updateState(mPreference);
-
-        assertThat(mPreference.isChecked()).isFalse();
-    }
-
-    @Test
-    public void updateState_autoSet_shouldNotCheckPreference() {
-        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null);
-
-        mController.updateState(mPreference);
-
-        assertThat(mPreference.isChecked()).isTrue();
-    }
-
-    @Test
-    public void updatePreference_autoSet_shouldSendIntent_12HourLocale() {
-        mController.setChecked(false);
-
-        List<Intent> intentsFired = mApplication.getBroadcastIntents();
-        assertThat(intentsFired.size()).isEqualTo(1);
-        Intent intentFired = intentsFired.get(0);
-        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
-        assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
-                .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR);
-    }
-
-    @Test
-    public void updatePreference_autoSet_shouldSendIntent_24HourLocale() {
-        mController.setIs24HourLocale(true);
-
-        mController.setChecked(false);
-
-        List<Intent> intentsFired = mApplication.getBroadcastIntents();
-        assertThat(intentsFired.size()).isEqualTo(1);
-        Intent intentFired = intentsFired.get(0);
-        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
-        assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
-                .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR);
-    }
-
-    @Test
-    public void updatePreference_24HourSet_shouldSendIntent() {
-        mController.setIs24HourLocale(false);
-
-        mController.setChecked(true);
-
-        List<Intent> intentsFired = mApplication.getBroadcastIntents();
-        assertThat(intentsFired.size()).isEqualTo(1);
-        Intent intentFired = intentsFired.get(0);
-        assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
-        assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
-                .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT);
-    }
-
-    /**
-     * Extend class under test to change {@link #is24HourLocale} to not call
-     * {@link DateFormat#is24HourLocale(Locale)} because that's not available in roboelectric.
-     */
-    private static class TestAutoTimeFormatPreferenceController
-            extends AutoTimeFormatPreferenceController {
-
-        private boolean is24HourLocale = false;
-
-        TestAutoTimeFormatPreferenceController(Context context, String preferenceKey) {
-            super(context, preferenceKey);
-        }
-
-        void setIs24HourLocale(boolean value) {
-            is24HourLocale = value;
-        }
-
-        @Override
-        boolean is24HourLocale(Locale locale) {
-            return is24HourLocale;
-        }
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
index 7bf8d52..2961935 100644
--- a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
@@ -40,17 +40,12 @@
 import android.app.time.TimeZoneDetectorStatus;
 import android.content.Context;
 import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.preference.Preference;
 
 import com.android.settings.R;
-import com.android.settings.flags.Flags;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -62,9 +57,6 @@
 @RunWith(RobolectricTestRunner.class)
 public class AutoTimeZonePreferenceControllerTest {
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     @Mock
     private UpdateTimeAndDateCallback mCallback;
     private Context mContext;
@@ -247,7 +239,6 @@
     }
 
     @Test
-    @EnableFlags({Flags.FLAG_REVAMP_TOGGLES})
     public void toggleOff_revampFlagOn_shouldToggleOffUseLocation() {
         TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
                 /* autoSupported= */ true,
@@ -266,25 +257,6 @@
         verify(mTimeManager).updateTimeZoneConfiguration(configuration);
     }
 
-    @Test
-    @DisableFlags({Flags.FLAG_REVAMP_TOGGLES})
-    public void toggleOff_revampFlagOff_shouldToggleOffUseLocation() {
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
-                /* autoSupported= */ true,
-                /* autoEnabled= */ true,
-                /* telephonySupported= */ true,
-                /* locationSupported= */ true);
-        when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
-
-        mController.setChecked(false);
-
-        TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
-                .setAutoDetectionEnabled(false)
-                .build();
-
-        verify(mTimeManager).updateTimeZoneConfiguration(configuration);
-    }
-
     private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
             boolean autoSupported, boolean autoEnabled, boolean telephonySupported) {
         return createCapabilitiesAndConfig(autoSupported, autoEnabled, telephonySupported, false);
diff --git a/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java
index 40794d2..2f23257 100644
--- a/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java
@@ -45,17 +45,13 @@
 import android.app.time.TimeZoneDetectorStatus;
 import android.content.Context;
 import android.os.UserHandle;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.preference.SwitchPreference;
 
 import com.android.settings.R;
 import com.android.settings.core.InstrumentedPreferenceFragment;
-import com.android.settings.flags.Flags;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -71,9 +67,6 @@
 })
 public class LocationTimeZoneDetectionPreferenceControllerTest {
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     @Mock
     private TimeManager mTimeManager;
     private Context mContext;
@@ -131,8 +124,7 @@
     }
 
     @Test
-    @EnableFlags({Flags.FLAG_REVAMP_TOGGLES})
-    public void flagRevampTogglesOn_toggleOff_automaticTimeZone_disablesLocationToggle() {
+    public void toggleOff_automaticTimeZone_disablesLocationToggle() {
         TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                 createTimeZoneCapabilitiesAndConfig(/* useLocationEnabled= */ true,
                         CAPABILITY_POSSESSED, /* setAutoDetectionEnabled= */ false);
diff --git a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
index c5aac84..a341922 100644
--- a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
@@ -23,16 +23,11 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
 import androidx.preference.SwitchPreference;
 
-import com.android.settings.flags.Flags;
-
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -46,9 +41,6 @@
 @RunWith(RobolectricTestRunner.class)
 public class TimeFormatPreferenceControllerTest {
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     @Mock
     private UpdateTimeAndDateCallback mCallback;
 
@@ -105,16 +97,6 @@
     }
 
     @Test
-    @DisableFlags({Flags.FLAG_REVAMP_TOGGLES})
-    public void updateState_autoSet_shouldNotEnablePreference() {
-        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null);
-
-        mController.updateState(mPreference);
-
-        assertThat(mPreference.isEnabled()).isFalse();
-    }
-
-    @Test
     public void updatePreference_12HourSet_shouldSendIntent() {
         mController.setChecked(false);
 
diff --git a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java
index a03ca61..1ce6c3f 100644
--- a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java
@@ -21,7 +21,6 @@
 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.os.SystemProperties;
@@ -32,8 +31,6 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.settings.R;
-import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
 import com.android.settingslib.search.SearchIndexableRaw;
 
 import org.junit.Before;
@@ -63,21 +60,6 @@
     }
 
     @Test
-    public void getTileTooltipContent_returnsExpectedValues() {
-        // Simulate to call getTileTooltipContent after onDetach
-        assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT))
-                .isNull();
-        // Simulate to call getTileTooltipContent after onAttach
-        when(mSettings.getContext()).thenReturn(mContext);
-        assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT))
-                .isEqualTo(mContext.getText(
-                        R.string.accessibility_one_handed_mode_qs_tooltip_content));
-        assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE))
-                .isEqualTo(mContext.getText(
-                        R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content));
-    }
-
-    @Test
     public void getLogTag_returnsCorrectTag() {
         assertThat(mSettings.getLogTag()).isEqualTo("OneHandedSettings");
     }
diff --git a/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java
index c3ccf34..96d9583 100644
--- a/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java
@@ -24,20 +24,29 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.R;
+import com.android.server.display.feature.flags.Flags;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+/** Tests for {@link ReduceBrightColorsIntensityPreferenceController} */
 @RunWith(AndroidJUnit4.class)
 public class ReduceBrightColorsIntensityPreferenceControllerTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private Context mContext;
     private Resources mResources;
     private ReduceBrightColorsIntensityPreferenceController mPreferenceController;
@@ -52,27 +61,119 @@
     }
 
     @Test
-    public void isAvailable_configuredRbcAvailable_enabledRbc_shouldReturnTrue() {
+    @DisableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOnAndAvailable_returnTrue() {
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
         doReturn(true).when(mResources).getBoolean(
                 R.bool.config_reduceBrightColorsAvailable);
+
         assertThat(mPreferenceController.isAvailable()).isTrue();
     }
+
     @Test
-    public void isAvailable_configuredRbcAvailable_disabledRbc_shouldReturnTrue() {
+    @DisableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOffAndAvailable_returnTrue() {
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
         doReturn(true).when(mResources).getBoolean(
                 R.bool.config_reduceBrightColorsAvailable);
+
         assertThat(mPreferenceController.isAvailable()).isTrue();
     }
+
     @Test
-    public void isAvailable_configuredRbcUnavailable_enabledRbc_shouldReturnFalse() {
+    @DisableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOnAndUnavailable_returnFalse() {
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
         doReturn(false).when(mResources).getBoolean(
                 R.bool.config_reduceBrightColorsAvailable);
+
+        assertThat(mPreferenceController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOnAndAvailable_returnTrue() {
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        doReturn(true).when(mResources).getBoolean(
+                R.bool.config_reduceBrightColorsAvailable);
+
+        assertThat(mPreferenceController.isAvailable()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOffAndAvailable_returnTrue() {
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
+        doReturn(true).when(mResources).getBoolean(
+                R.bool.config_reduceBrightColorsAvailable);
+
+        assertThat(mPreferenceController.isAvailable()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOnAndUnavailable_returnFalse() {
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        doReturn(false).when(mResources).getBoolean(
+                R.bool.config_reduceBrightColorsAvailable);
+
+        assertThat(mPreferenceController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOnAndAvailable_returnFalse() {
+        doReturn(true).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        doReturn(true).when(mResources).getBoolean(
+                R.bool.config_reduceBrightColorsAvailable);
+
+        assertThat(mPreferenceController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOffAndAvailable_returnFalse() {
+        doReturn(true).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
+        doReturn(true).when(mResources).getBoolean(
+                R.bool.config_reduceBrightColorsAvailable);
+
+        assertThat(mPreferenceController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+    public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOnAndUnavailable_returnFalse() {
+        doReturn(true).when(mResources).getBoolean(
+                com.android.internal.R.bool.config_evenDimmerEnabled);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        doReturn(false).when(mResources).getBoolean(
+                R.bool.config_reduceBrightColorsAvailable);
+
         assertThat(mPreferenceController.isAvailable()).isFalse();
     }
 
diff --git a/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java b/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java
index 656fa27..2209355 100644
--- a/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java
@@ -21,18 +21,24 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.Settings;
+import com.android.settings.flags.Flags;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 public class LanguagePreferenceControllerTest {
     private Context mContext;
     private LanguagePreferenceController mController;
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setup() {
         mContext = ApplicationProvider.getApplicationContext();
@@ -40,6 +46,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_REGIONAL_PREFERENCES_API_ENABLED)
     public void getAvailabilityStatus_featureFlagOff_LanguageSettingsActivitydisabled() {
         mController.getAvailabilityStatus();