Merge "Avoid launching captive portal pages when uri is empty" into main
diff --git a/Android.bp b/Android.bp
index 8b903ba..0a58ee8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -94,8 +94,10 @@
         "MediaDrmSettingsFlagsLib",
         "Settings-change-ids",
         "SettingsLib",
-        "SettingsLibDataStore",
         "SettingsLibActivityEmbedding",
+        "SettingsLibDataStore",
+        "SettingsLibMetadata",
+        "SettingsLibPreference",
         "aconfig_settings_flags_lib",
         "accessibility_settings_flags_lib",
         "contextualcards",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 66c3beb..fd40e90 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -783,9 +783,13 @@
 
         <activity android:name="Settings$CombinedBiometricSettingsActivity"
                   android:label="@string/security_settings_biometric_preference_title"
-                  android:exported="false"
+                  android:exported="true"
                   android:enableOnBackInvokedCallback="false"
                   android:taskAffinity="com.android.settings.root">
+            <intent-filter>
+                <action android:name="android.settings.COMBINED_BIOMETRICS_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                        android:value="com.android.settings.biometrics.combination.CombinedBiometricSettings" />
             <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
@@ -1293,7 +1297,7 @@
         <activity
             android:name="Settings$ModesSettingsActivity"
             android:label="@string/zen_modes_list_title"
-            android:icon="@drawable/ic_homepage_notification"
+            android:icon="@*android:drawable/ic_zen_priority_modes"
             android:exported="true">
             <intent-filter android:priority="1"
                            android:featureFlag="android.app.modes_ui">
@@ -2809,6 +2813,9 @@
         <activity android:name=".biometrics.fingerprint.FingerprintEnrollFinish" android:exported="false"/>
         <activity android:name=".biometrics.fingerprint.FingerprintEnrollParentalConsent" android:exported="false"/>
         <activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroduction"
+            android:exported="false"
+            android:theme="@style/GlifTheme.Light" />
+        <activity android:name=".biometrics.fingerprint.FingerprintEnroll"
             android:exported="true"
             android:theme="@style/GlifTheme.Light">
             <intent-filter>
@@ -2819,9 +2826,13 @@
         </activity>
 
         <activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroductionInternal"
-                  android:exported="false"
-                  android:theme="@style/GlifTheme.Light"
-                  android:taskAffinity="com.android.settings.root" />
+              android:exported="false"
+              android:theme="@style/GlifTheme.Light"
+              android:taskAffinity="com.android.settings.root" />
+        <activity android:name=".biometrics.fingerprint.FingerprintEnroll$InternalActivity"
+            android:exported="false"
+            android:theme="@style/GlifTheme.Light"
+            android:taskAffinity="com.android.settings.root" />
 
         <activity android:name=".biometrics.fingerprint.SetupFingerprintEnrollFindSensor"
             android:exported="false"
@@ -2829,6 +2840,10 @@
         <activity android:name=".biometrics.fingerprint.SetupFingerprintEnrollEnrolling" android:exported="false"/>
         <activity android:name=".biometrics.fingerprint.SetupFingerprintEnrollFinish" android:exported="false"/>
         <activity android:name=".biometrics.fingerprint.SetupFingerprintEnrollIntroduction"
+            android:exported="false"
+            android:permission="android.permission.MANAGE_FINGERPRINT"
+            android:theme="@style/GlifTheme.Light" />
+        <activity android:name=".biometrics.fingerprint.FingerprintEnroll$SetupActivity"
             android:exported="true"
             android:permission="android.permission.MANAGE_FINGERPRINT"
             android:theme="@style/GlifTheme.Light">
@@ -2838,7 +2853,6 @@
             </intent-filter>
         </activity>
 
-
         <activity android:name=".biometrics.fingerprint2.ui.enrollment.activity.FingerprintEnrollmentV2Activity"
             android:exported="true"
             android:permission="android.permission.MANAGE_FINGERPRINT"
diff --git a/res/drawable/ic_pointer_and_touchpad.xml b/res/drawable/ic_pointer_and_touchpad.xml
new file mode 100644
index 0000000..c077900
--- /dev/null
+++ b/res/drawable/ic_pointer_and_touchpad.xml
@@ -0,0 +1,35 @@
+<!--
+  Copyright 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <com.android.settingslib.widget.AdaptiveIconShapeDrawable
+            android:width="@dimen/accessibility_icon_size"
+            android:height="@dimen/accessibility_icon_size"
+            android:color="@color/accessibility_feature_background"/>
+    </item>
+    <item android:gravity="center">
+        <vector
+            android:height="32dp"
+            android:width="32dp"
+            android:viewportHeight="32"
+            android:viewportWidth="32">
+            <path
+                android:fillColor="#fff"
+                android:pathData="M19.15,22.35a2.6,2.6 0,0 0,1.91 -0.79,2.6 2.6,0 0,0 0.79,-1.91v-0.9h-5.4v0.9c0,0.75 0.26,1.39 0.79,1.91a2.6,2.6 0,0 0,1.91 0.79ZM16.49,17.4h1.98v-2.17a2.6,2.6 0,0 0,-1.98 2.17ZM19.82,17.4h2a2.6,2.6 0,0 0,-0.68 -1.39,2.62 2.62,0 0,0 -1.32,-0.78v2.17ZM19.15,23.7a3.96,3.96 0,0 1,-4.05 -4.05v-1.8c0,-1.14 0.39,-2.1 1.16,-2.87a3.93,3.93 0,0 1,2.89 -1.18c1.14,0 2.1,0.4 2.87,1.18a3.87,3.87 0,0 1,1.18 2.87v1.8c0,1.14 -0.4,2.1 -1.18,2.89a3.9,3.9 0,0 1,-2.87 1.16ZM10.15,20.55v-8.1,8.1ZM10.15,21.9c-0.38,0 -0.7,-0.13 -0.96,-0.4s-0.39,-0.59 -0.39,-0.95v-8.1c0,-0.36 0.13,-0.67 0.4,-0.94 0.26,-0.27 0.57,-0.41 0.95,-0.41h11.7c0.37,0 0.7,0.14 0.96,0.41 0.26,0.27 0.39,0.58 0.39,0.94L10.15,12.45v8.1h3.6v1.35h-3.6Z"/>
+        </vector>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3494afd..887491d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4532,8 +4532,8 @@
     <string name="trackpad_bottom_right_tap_summary">Click in the bottom right corner of the touchpad for more options</string>
     <!-- Title text for 'Pointer speed'. [CHAR LIMIT=35] -->
     <string name="trackpad_pointer_speed">Pointer speed</string>
-    <!-- Title text for mouse pointer fill style. [CHAR LIMIT=35] -->
-    <string name="pointer_fill_style">Pointer fill style</string>
+    <!-- Title text for mouse pointer color. [CHAR LIMIT=35] -->
+    <string name="pointer_fill_style">Pointer color</string>
     <!-- Content description for black pointer fill style. [CHAR LIMIT=60] -->
     <string name="pointer_fill_style_black_button">Change pointer fill style to black</string>
     <!-- Content description for green pointer fill style. [CHAR LIMIT=60] -->
@@ -4810,6 +4810,12 @@
     <string name="display_category_title">Display</string>
     <!-- Title for the accessibility color and motion page. [CHAR LIMIT=50] -->
     <string name="accessibility_color_and_motion_title">Color and motion</string>
+    <!-- Title for the accessibility pointer and touchpad page. [CHAR LIMIT=50] -->
+    <string name="accessibility_pointer_and_touchpad_title">Pointer &amp; touchpad accessibility</string>
+    <!-- Summary for the accessibility pointer and touchpad page. [CHAR LIMIT=50] -->
+    <string name="accessibility_pointer_and_touchpad_summary">Pointer color, pointer size &amp; more</string>
+    <!-- Title for the accessibility pointer color customization page. [CHAR LIMIT=50] -->
+    <string name="accessibility_pointer_color_customization_title">Pointer color customization</string>
     <!-- Title for the accessibility color contrast page. [CHAR LIMIT=50] -->
     <string name="accessibility_color_contrast_title">Color contrast</string>
     <!-- Intro for the accessibility color contrast page. [CHAR LIMIT=NONE] -->
@@ -7861,7 +7867,9 @@
     <string name="keywords_keyboard_vibration">keyboard, haptics, vibrate,</string>
 
     <!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
-    <string name="sound_dashboard_summary">Volume, vibration, Do Not Disturb</string>
+    <string name="sound_dashboard_summary">Volume and vibration</string>
+    <!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
+    <string name="sound_dashboard_summary_with_dnd">Volume, vibration, Do Not Disturb</string>
 
     <!-- Sound: Title for the option managing media volume. [CHAR LIMIT=30] -->
     <string name="media_volume_option_title">Media volume</string>
@@ -13360,9 +13368,9 @@
     <!-- Summary of the Live Caption enabled state. -->
     <string name="live_caption_enabled">On</string>
     <!-- State description for the Audio Balance seek bar, with left reported before right. -->
-    <string name="audio_seek_bar_state_left_first">Audio %1$d%% left, %2$d%% right</string>
+    <string name="audio_seek_bar_state_left_first">Audio <xliff:g id="percent_left">%1$s</xliff:g> left, <xliff:g id="percent_right">%2$s</xliff:g> right</string>
     <!-- State description for the Audio Balance seek bar, with right reported before left. -->
-    <string name="audio_seek_bar_state_right_first">Audio %1$d%% right, %2$d%% left</string>
+    <string name="audio_seek_bar_state_right_first">Audio <xliff:g id="percent_right">%1$s</xliff:g> right, <xliff:g id="percent_left">%2$s</xliff:g> left</string>
 
     <!--  Warning text about the visibility of device name. [CHAR LIMIT=NONE] -->
     <string name="about_phone_device_name_warning">Your device name is visible to apps you installed. It may also be seen by other people when you connect to Bluetooth devices, connect to a Wi-Fi network or set up a Wi-Fi hotspot.</string>
@@ -13571,6 +13579,8 @@
     <string name="audio_streams_dialog_cannot_play">Can\u0027t play this audio stream on <xliff:g example="LE headset" id="device_name">%1$s</xliff:g>.</string>
     <!-- The preference summary when add source succeed [CHAR LIMIT=NONE] -->
     <string name="audio_streams_listening_now">Listening now</string>
+    <!-- The preference summary when source is present on sinks [CHAR LIMIT=NONE] -->
+    <string name="audio_streams_present_now">Paused by host</string>
     <!-- Le audio streams service notification leave broadcast text [CHAR LIMIT=NONE] -->
     <string name="audio_streams_media_service_notification_leave_broadcast_text">Stop listening</string>
     <!-- Le audio streams no le device dialog title [CHAR LIMIT=NONE] -->
diff --git a/res/xml/accessibility_color_and_motion.xml b/res/xml/accessibility_color_and_motion.xml
index 4c4490c..a500b72 100644
--- a/res/xml/accessibility_color_and_motion.xml
+++ b/res/xml/accessibility_color_and_motion.xml
@@ -72,17 +72,6 @@
         android:title="@string/accessibility_toggle_large_pointer_icon_title"
         settings:controller="com.android.settings.accessibility.LargePointerIconPreferenceController"/>
 
-    <com.android.settings.widget.LabeledSeekBarPreference
-        android:key="large_pointer_scale"
-        android:title="@string/accessibility_toggle_large_pointer_icon_title"
-        android:summary="@string/accessibility_toggle_large_pointer_icon_summary"
-        android:max="@integer/pointer_scale_seek_bar_end"
-        settings:iconStart="@drawable/ic_remove_24dp"
-        settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
-        settings:iconEnd="@drawable/ic_add_24dp"
-        settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
-        settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
-
     <PreferenceCategory
         android:key="experimental_category"
         android:persistent="false"
diff --git a/res/xml/accessibility_pointer_and_touchpad.xml b/res/xml/accessibility_pointer_and_touchpad.xml
new file mode 100644
index 0000000..1d3b0f2
--- /dev/null
+++ b/res/xml/accessibility_pointer_and_touchpad.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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:key="accessibility_pointer_and_touchpad"
+    android:persistent="false"
+    android:title="@string/accessibility_pointer_and_touchpad_title">
+
+    <com.android.settings.widget.LabeledSeekBarPreference
+        android:key="pointer_scale_preference"
+        android:title="@string/pointer_scale"
+        android:max="@integer/pointer_scale_seek_bar_end"
+        settings:iconStart="@drawable/ic_remove_24dp"
+        settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
+        settings:iconEnd="@drawable/ic_add_24dp"
+        settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
+        settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
+
+    <Preference
+        android:fragment="com.android.settings.inputmethod.PointerColorCustomizationFragment"
+        android:key="pointer_color_customization_preference"
+        android:persistent="false"
+        android:title="@string/accessibility_pointer_color_customization_title"/>
+
+    <Preference
+        android:fragment="com.android.settings.accessibility.ToggleAutoclickPreferenceFragment"
+        android:key="autoclick_preference"
+        android:persistent="false"
+        android:title="@string/accessibility_autoclick_preference_title"
+        settings:keywords="@string/keywords_auto_click"
+        settings:controller="com.android.settings.accessibility.AutoclickPreferenceController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/accessibility_pointer_color_customization.xml b/res/xml/accessibility_pointer_color_customization.xml
new file mode 100644
index 0000000..6d767b4
--- /dev/null
+++ b/res/xml/accessibility_pointer_color_customization.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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:key="accessibility_pointer_color_customization"
+    android:persistent="false"
+    android:title="@string/accessibility_pointer_color_customization_title">
+
+    <com.android.settings.inputmethod.PointerFillStylePreference
+        android:key="pointer_fill_style"
+        android:title="@string/pointer_fill_style"
+        settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
+
+    <com.android.settings.inputmethod.PointerStrokeStylePreference
+        android:key="pointer_stroke_style"
+        android:title="@string/pointer_stroke_style"
+        settings:controller="com.android.settings.inputmethod.PointerStrokeStylePreferenceController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml
index 6561775..18e6455 100644
--- a/res/xml/accessibility_settings.xml
+++ b/res/xml/accessibility_settings.xml
@@ -110,6 +110,16 @@
             settings:keywords="@string/keywords_vibration"
             android:summary="@string/accessibility_vibration_settings_summary"/>
 
+        <Preference
+            android:fragment="com.android.settings.inputmethod.PointerTouchpadFragment"
+            android:key="pointer_and_touchpad"
+            android:icon="@drawable/ic_pointer_and_touchpad"
+            android:persistent="false"
+            android:title="@string/accessibility_pointer_and_touchpad_title"
+            android:summary="@string/accessibility_pointer_and_touchpad_summary"
+            settings:controller="com.android.settings.inputmethod.PointerTouchpadPreferenceController"
+            settings:searchable="true"/>
+
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/res/xml/accessibility_tap_assistance.xml b/res/xml/accessibility_tap_assistance.xml
index d2ec653..6a53d83 100644
--- a/res/xml/accessibility_tap_assistance.xml
+++ b/res/xml/accessibility_tap_assistance.xml
@@ -37,12 +37,4 @@
         android:title="@string/accessibility_setting_item_control_timeout_title"
         settings:controller="com.android.settings.accessibility.AccessibilityTimeoutPreferenceController"
         settings:keywords="@string/keywords_accessibility_timeout"/>
-
-    <Preference
-        android:fragment="com.android.settings.accessibility.ToggleAutoclickPreferenceFragment"
-        android:key="autoclick_preference"
-        android:persistent="false"
-        android:title="@string/accessibility_autoclick_preference_title"
-        settings:keywords="@string/keywords_auto_click"
-        settings:controller="com.android.settings.accessibility.AutoclickPreferenceController"/>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml
index 1ec968a..44fe7fc 100644
--- a/res/xml/top_level_settings.xml
+++ b/res/xml/top_level_settings.xml
@@ -104,8 +104,9 @@
         android:key="top_level_sound"
         android:order="-90"
         android:title="@string/sound_settings"
-        android:summary="@string/sound_dashboard_summary"
-        settings:highlightableMenuKey="@string/menu_key_sound"/>
+        android:summary="@string/sound_dashboard_summary_with_dnd"
+        settings:highlightableMenuKey="@string/menu_key_sound"
+        settings:controller="com.android.settings.sound.TopLevelSoundPreferenceController"/>
 
     <com.android.settings.widget.HomepagePreference
         android:fragment="com.android.settings.DisplaySettings"
diff --git a/res/xml/top_level_settings_v2.xml b/res/xml/top_level_settings_v2.xml
index fe98a71..4bc66f6 100644
--- a/res/xml/top_level_settings_v2.xml
+++ b/res/xml/top_level_settings_v2.xml
@@ -79,8 +79,9 @@
             android:key="top_level_sound"
             android:order="-40"
             android:title="@string/sound_settings"
-            android:summary="@string/sound_dashboard_summary"
-            settings:highlightableMenuKey="@string/menu_key_sound"/>
+            android:summary="@string/sound_dashboard_summary_with_dnd"
+            settings:highlightableMenuKey="@string/menu_key_sound"
+            settings:controller="com.android.settings.sound.TopLevelSoundPreferenceController"/>
 
         <com.android.settings.widget.RestrictedHomepagePreference
             android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml
index 935de82..7e94944 100644
--- a/res/xml/trackpad_settings.xml
+++ b/res/xml/trackpad_settings.xml
@@ -62,29 +62,14 @@
         android:selectable="false"
         settings:controller="com.android.settings.inputmethod.TrackpadPointerSpeedPreferenceController"/>
 
-    <com.android.settings.inputmethod.PointerFillStylePreference
-        android:key="pointer_fill_style"
-        android:title="@string/pointer_fill_style"
+    <Preference
+        android:fragment="com.android.settings.inputmethod.PointerTouchpadFragment"
+        android:key="pointer_and_touchpad"
         android:order="50"
-        settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
-
-    <com.android.settings.inputmethod.PointerStrokeStylePreference
-        android:key="pointer_stroke_style"
-        android:title="@string/pointer_stroke_style"
-        android:order="60"
-        settings:controller="com.android.settings.inputmethod.PointerStrokeStylePreferenceController"/>
-
-    <com.android.settings.widget.LabeledSeekBarPreference
-        android:key="pointer_scale"
-        android:title="@string/pointer_scale"
-        android:order="70"
-        android:max="@integer/pointer_scale_seek_bar_end"
-        settings:iconStart="@drawable/ic_remove_24dp"
-        settings:searchable="false"
-        settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
-        settings:iconEnd="@drawable/ic_add_24dp"
-        settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
-        settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
+        android:persistent="false"
+        android:title="@string/accessibility_pointer_and_touchpad_title"
+        android:summary="@string/accessibility_pointer_and_touchpad_summary"
+        settings:searchable="true"/>
 
     <com.android.settingslib.widget.ButtonPreference
         android:key="trackpad_touch_gesture"
diff --git a/src/com/android/settings/FallbackHome.java b/src/com/android/settings/FallbackHome.java
index b70470b..d62b6c2 100644
--- a/src/com/android/settings/FallbackHome.java
+++ b/src/com/android/settings/FallbackHome.java
@@ -179,6 +179,8 @@
                         SystemClock.uptimeMillis(), false);
                 finish();
             }
+        } else {
+            Log.d(TAG, "User not yet unlocked");
         }
     }
 
diff --git a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
index 914d9cf..a8e456d 100644
--- a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
@@ -26,6 +26,7 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.core.content.ContextCompat;
 
 import com.android.settings.R;
@@ -101,6 +102,11 @@
         return mLabel;
     }
 
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     private Drawable getA11yActivityIcon() {
         ActivityInfo activityInfo = mA11yShortcutInfo.getActivityInfo();
         Drawable serviceIcon;
diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
index 6aa8c84..6a0b5e2 100644
--- a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
+++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java
@@ -16,8 +16,12 @@
 
 package com.android.settings.accessibility;
 
+import android.content.ComponentName;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.List;
@@ -28,10 +32,22 @@
 public interface AccessibilitySearchFeatureProvider {
 
     /**
-     * Returns a list of raw data for indexing. See {@link SearchIndexableRaw}
+     * Returns accessibility features to be searched where the accessibility features are always on
+     * the device and their feature names won't change.
      *
      * @param context a valid context {@link Context} instance
-     * @return a list of {@link SearchIndexableRaw} references. Can be null.
+     * @return a list of {@link SearchIndexableRaw} references
      */
+    @Nullable
     List<SearchIndexableRaw> getSearchIndexableRawData(Context context);
+
+    /**
+     * Returns synonyms of the Accessibility component that is used for search.
+     *
+     * @param context the context that is used for grabbing resources
+     * @param componentName the ComponentName of the accessibility feature
+     * @return a comma separated synonyms e.g. "wifi, wi-fi, network connection"
+     */
+    @NonNull
+    String getSynonymsForComponent(@NonNull Context context, @NonNull ComponentName componentName);
 }
diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
index c358af1..94594a1 100644
--- a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
+++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java
@@ -16,8 +16,12 @@
 
 package com.android.settings.accessibility;
 
+import android.content.ComponentName;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.search.SearchIndexableRaw;
 
 import java.util.List;
@@ -27,8 +31,16 @@
  */
 public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider {
 
+    @Nullable
     @Override
     public List<SearchIndexableRaw> getSearchIndexableRawData(Context context) {
         return null;
     }
+
+    @NonNull
+    @Override
+    public String getSynonymsForComponent(@NonNull Context context,
+            @NonNull ComponentName componentName) {
+        return "";
+    }
 }
diff --git a/src/com/android/settings/accessibility/AccessibilityServicePreference.java b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
index c1dfae8..8a22d82 100644
--- a/src/com/android/settings/accessibility/AccessibilityServicePreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
@@ -26,6 +26,7 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.core.content.ContextCompat;
 
 import com.android.settings.R;
@@ -95,6 +96,11 @@
         super.performClick();
     }
 
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     private Drawable getA11yServiceIcon() {
         ResolveInfo resolveInfo = mA11yServiceInfo.getResolveInfo();
         Drawable serviceIcon;
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 8de4936..db8f937 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -473,7 +473,7 @@
      * @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s.
      * @param installedServiceList  A list of installed {@link AccessibilityServiceInfo}s.
      */
-    private List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
+    private static List<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
             List<AccessibilityShortcutInfo> installedShortcutList,
             List<AccessibilityServiceInfo> installedServiceList) {
         final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
@@ -623,6 +623,51 @@
                             .getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
                                     context);
                 }
+
+                @Override
+                public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context,
+                        boolean enabled) {
+                    List<SearchIndexableRaw> dynamicRawData = super.getDynamicRawDataToIndex(
+                            context, enabled);
+                    if (dynamicRawData == null) {
+                        dynamicRawData = new ArrayList<>();
+                    }
+                    if (!Flags.fixA11ySettingsSearch()) {
+                        return dynamicRawData;
+                    }
+
+                    AccessibilityManager a11yManager = context.getSystemService(
+                            AccessibilityManager.class);
+                    AccessibilitySearchFeatureProvider a11ySearchFeatureProvider =
+                            FeatureFactory.getFeatureFactory()
+                                    .getAccessibilitySearchFeatureProvider();
+                    List<RestrictedPreference> installedA11yFeaturesPref =
+                            AccessibilitySettings.getInstalledAccessibilityPreferences(
+                                    context,
+                                    a11yManager.getInstalledAccessibilityShortcutListAsUser(
+                                            context, UserHandle.myUserId()),
+                                    a11yManager.getInstalledAccessibilityServiceList()
+                            );
+                    for (RestrictedPreference pref : installedA11yFeaturesPref) {
+                        SearchIndexableRaw indexableRaw = new SearchIndexableRaw(context);
+                        indexableRaw.key = pref.getKey();
+                        indexableRaw.title = pref.getTitle().toString();
+                        @NonNull String synonyms = "";
+                        if (pref instanceof AccessibilityServicePreference) {
+                            synonyms = a11ySearchFeatureProvider.getSynonymsForComponent(
+                                    context,
+                                    ((AccessibilityServicePreference) pref).getComponentName());
+                        } else if (pref instanceof AccessibilityActivityPreference) {
+                            synonyms = a11ySearchFeatureProvider.getSynonymsForComponent(
+                                    context,
+                                    ((AccessibilityActivityPreference) pref).getComponentName());
+                        }
+                        indexableRaw.keywords = synonyms;
+                        dynamicRawData.add(indexableRaw);
+                    }
+
+                    return dynamicRawData;
+                }
             };
 
     @Override
diff --git a/src/com/android/settings/accessibility/BalanceSeekBar.java b/src/com/android/settings/accessibility/BalanceSeekBar.java
index 7441d6f..8f8f767 100644
--- a/src/com/android/settings/accessibility/BalanceSeekBar.java
+++ b/src/com/android/settings/accessibility/BalanceSeekBar.java
@@ -36,6 +36,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 
 /**
  * A custom seekbar for the balance setting.
@@ -178,10 +179,12 @@
                 == LAYOUT_DIRECTION_RTL;
         final int rightPercent = (int) (100 * (progress / max));
         final int leftPercent = 100 - rightPercent;
+        final String rightPercentString = Utils.formatPercentage(rightPercent);
+        final String leftPercentString = Utils.formatPercentage(leftPercent);
         if (rightPercent > leftPercent || (rightPercent == leftPercent && isLayoutRtl)) {
-            return context.getString(resIdRightFirst, rightPercent, leftPercent);
+            return context.getString(resIdRightFirst, rightPercentString, leftPercentString);
         } else {
-            return context.getString(resIdLeftFirst, leftPercent, rightPercent);
+            return context.getString(resIdLeftFirst, leftPercentString, rightPercentString);
         }
     }
 }
diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
index 70882a4..2ef0858 100644
--- a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
+++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
@@ -67,14 +67,11 @@
 
     @Override
     public CharSequence getSummary() {
-        if (mPreference != null) {
-            return mPreference.isEnabled()
-                    ? "%s"
-                    : mContext.getString(
-                            R.string.accessibility_button_disabled_button_mode_summary);
-        } else {
-            return "%s";
+        int rId = R.string.accessibility_button_fade_summary;
+        if (mPreference != null && !mPreference.isEnabled()) {
+            rId = R.string.accessibility_button_disabled_button_mode_summary;
         }
+        return mContext.getString(rId);
     }
 
     @Override
diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
index 72a0f08..b38c42c 100644
--- a/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
+++ b/src/com/android/settings/activityembedding/ActivityEmbeddingRulesController.java
@@ -40,9 +40,8 @@
 import com.android.settings.SubSettings;
 import com.android.settings.biometrics.face.FaceEnrollIntroduction;
 import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal;
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollActivityClassProvider;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
-import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
-import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal;
 import com.android.settings.core.FeatureFlags;
 import com.android.settings.homepage.DeepLinkHomepageActivity;
 import com.android.settings.homepage.DeepLinkHomepageActivityInternal;
@@ -255,8 +254,12 @@
                     .buildSearchIntent(mContext, SettingsEnums.SETTINGS_HOMEPAGE);
             addActivityFilter(activityFilters, searchIntent);
         }
-        addActivityFilter(activityFilters, FingerprintEnrollIntroduction.class);
-        addActivityFilter(activityFilters, FingerprintEnrollIntroductionInternal.class);
+        final FingerprintEnrollActivityClassProvider fpClassProvider = FeatureFactory
+                .getFeatureFactory()
+                .getFingerprintFeatureProvider()
+                .getEnrollActivityClassProvider();
+        addActivityFilter(activityFilters, fpClassProvider.getDefault());
+        addActivityFilter(activityFilters, fpClassProvider.getInternal());
         addActivityFilter(activityFilters, FingerprintEnrollEnrolling.class);
         addActivityFilter(activityFilters, FaceEnrollIntroductionInternal.class);
         addActivityFilter(activityFilters, FaceEnrollIntroduction.class);
diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java
index 09b2dba..2a457f5 100644
--- a/src/com/android/settings/biometrics/BiometricUtils.java
+++ b/src/com/android/settings/biometrics/BiometricUtils.java
@@ -44,10 +44,9 @@
 import com.android.settings.R;
 import com.android.settings.SetupWizardUtils;
 import com.android.settings.biometrics.face.FaceEnrollIntroduction;
+import com.android.settings.biometrics.fingerprint.FingerprintEnroll;
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor;
-import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction;
 import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor;
-import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollIntroduction;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockGeneric;
 import com.android.settings.password.ChooseLockSettingsHelper;
@@ -262,13 +261,13 @@
     /**
      * @param context caller's context
      * @param activityIntent The intent that started the caller's activity
-     * @return Intent for starting FingerprintEnrollIntroduction
+     * @return Intent for starting FingerprintEnroll
      */
     public static Intent getFingerprintIntroIntent(@NonNull Context context,
             @NonNull Intent activityIntent) {
         final boolean isSuw = WizardManagerHelper.isAnySetupWizard(activityIntent);
         final Intent intent = new Intent(context, isSuw
-                ? SetupFingerprintEnrollIntroduction.class : FingerprintEnrollIntroduction.class);
+                ? FingerprintEnroll.SetupActivity.class : FingerprintEnroll.class);
         if (isSuw) {
             WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
         }
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt b/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt
new file mode 100644
index 0000000..3d78269
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.biometrics.fingerprint
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
+
+/**
+ * Default class for handling fingerprint enrollment, designed to launch a subsequent activity and
+ * forward the result, then finish itself.
+ */
+open class FingerprintEnroll: AppCompatActivity() {
+
+    /** Inner class representing enrolling fingerprint enrollment in SetupWizard environment */
+    class SetupActivity : FingerprintEnroll() {
+        override val nextActivityClass: Class<*>
+            get() = enrollActivityProvider.setup
+    }
+
+    /** Inner class representing enrolling fingerprint enrollment from FingerprintSettings */
+    class InternalActivity : FingerprintEnroll() {
+        override val nextActivityClass: Class<*>
+        get() = enrollActivityProvider.internal
+    }
+
+    /**
+     * The class of the next activity to launch. This is open to allow subclasses to provide their
+     * own behavior. Defaults to the default activity class provided by the
+     * enrollActivityClassProvider.
+     */
+    open val nextActivityClass: Class<*>
+        get() = enrollActivityProvider.default
+
+    protected val enrollActivityProvider: FingerprintEnrollActivityClassProvider
+        get() = featureFactory.fingerprintFeatureProvider.enrollActivityClassProvider
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        /**
+         *  Logs the next activity to be launched, creates an intent for that activity,
+         *  adds flags to forward the result, includes any existing extras from the current intent,
+         *  starts the new activity and then finishes the current one
+         */
+        Log.d("FingerprintEnroll", "forward to $nextActivityClass")
+        val nextIntent = Intent(this, nextActivityClass)
+        nextIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
+        nextIntent.putExtras(intent)
+        startActivity(nextIntent)
+        finish()
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollActivityClassProvider.kt b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollActivityClassProvider.kt
new file mode 100644
index 0000000..853a3df
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollActivityClassProvider.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.biometrics.fingerprint
+
+import android.app.Activity
+
+open class FingerprintEnrollActivityClassProvider {
+
+    open val default: Class<out Activity>
+        get() = FingerprintEnrollIntroduction::class.java
+    open val setup: Class<out Activity>
+        get() = SetupFingerprintEnrollIntroduction::class.java
+    open val internal: Class<out Activity>
+        get() = FingerprintEnrollIntroductionInternal::class.java
+
+    companion object {
+        @JvmStatic
+        val instance = FingerprintEnrollActivityClassProvider()
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
index c1e34a5..baa88b5 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
@@ -33,7 +33,6 @@
      */
     SfpsEnrollmentFeature getSfpsEnrollmentFeature();
 
-
     /**
      * Gets calibrator for udfps pre-enroll
      * @param appContext application context
@@ -52,4 +51,13 @@
      * @return the feature implementation
      */
     SfpsRestToUnlockFeature getSfpsRestToUnlockFeature(@NonNull Context context);
+
+    /**
+     * Gets the provider for current fingerprint enrollment activity classes
+     * @return the provider
+     */
+    @NonNull
+    default FingerprintEnrollActivityClassProvider getEnrollActivityClassProvider() {
+        return FingerprintEnrollActivityClassProvider.getInstance();
+    }
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index 125691f..20d453f 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -1142,7 +1142,7 @@
         private void addFirstFingerprint(@Nullable Long gkPwHandle) {
             Intent intent = new Intent();
             intent.setClassName(SETTINGS_PACKAGE_NAME,
-                    FingerprintEnrollIntroductionInternal.class.getName());
+                    FingerprintEnroll.InternalActivity.class.getName());
             intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true);
             intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
                     SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
index 241eaea..d9289d6 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
@@ -43,7 +43,7 @@
 import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY
 import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
-import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
+import com.android.settings.biometrics.fingerprint.FingerprintEnroll.InternalActivity
 import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
@@ -514,7 +514,7 @@
     val intent = Intent()
     intent.setClassName(
       SETTINGS_PACKAGE_NAME,
-      FingerprintEnrollIntroductionInternal::class.java.name,
+      InternalActivity::class.java.name,
     )
     intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true)
     intent.putExtra(
diff --git a/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt b/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt
index 87e2e8b..5987e5a 100644
--- a/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt
+++ b/src/com/android/settings/bluetooth/ui/layout/DeviceSettingLayout.kt
@@ -22,4 +22,7 @@
 data class DeviceSettingLayout(val rows: List<DeviceSettingLayoutRow>)
 
 /** Represent a row in the layout. */
-data class DeviceSettingLayoutRow(val settingIds: Flow<List<Int>>)
+data class DeviceSettingLayoutRow(val columns: Flow<List<DeviceSettingLayoutColumn>>)
+
+/** Represent a column in a row. */
+data class DeviceSettingLayoutColumn(val settingId: Int, val highlighted: Boolean)
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
index f2a569d..a5997e7 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
@@ -20,12 +20,23 @@
 import android.content.Context
 import android.media.AudioManager
 import android.os.Bundle
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
@@ -43,7 +54,6 @@
 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 import com.android.settings.spa.preference.ComposePreference
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -91,10 +101,16 @@
 ) : DeviceDetailsFragmentFormatter {
     private val repository =
         featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
-            context, bluetoothAdapter, fragment.lifecycleScope)
+            context,
+            bluetoothAdapter,
+            fragment.lifecycleScope,
+        )
     private val spatialAudioInteractor =
         featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
-            context, context.getSystemService(AudioManager::class.java), fragment.lifecycleScope)
+            context,
+            context.getSystemService(AudioManager::class.java),
+            fragment.lifecycleScope,
+        )
     private val viewModel: BluetoothDeviceDetailsViewModel =
         ViewModelProvider(
                 fragment,
@@ -104,7 +120,8 @@
                     spatialAudioInteractor,
                     cachedDevice,
                     backgroundCoroutineContext,
-                ))
+                ),
+            )
             .get(BluetoothDeviceDetailsViewModel::class.java)
 
     override fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>? =
@@ -120,7 +137,8 @@
             viewModel
                 .getItems(fragmentType)
                 ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem>()
-                ?.first()?.invisibleProfiles
+                ?.first()
+                ?.invisibleProfiles
         }
 
     /** Updates bluetooth device details fragment layout. */
@@ -144,7 +162,8 @@
             val settingId = items[row].settingId
             if (settingIdToXmlPreferences.containsKey(settingId)) {
                 fragment.preferenceScreen.addPreference(
-                    settingIdToXmlPreferences[settingId]!!.apply { order = row })
+                    settingIdToXmlPreferences[settingId]!!.apply { order = row }
+                )
             } else {
                 val pref =
                     ComposePreference(context)
@@ -169,7 +188,8 @@
             emitAll(
                 viewModel.getDeviceSetting(cachedDevice, item.settingId).map {
                     it as? DeviceSettingPreferenceModel.HelpPreference
-                })
+                }
+            )
         } ?: emit(null)
     }
 
@@ -177,22 +197,56 @@
     private fun buildPreference(layout: DeviceSettingLayout, row: Int) {
         val contents by
             remember(row) {
-                    layout.rows[row].settingIds.flatMapLatest { settingIds ->
-                        if (settingIds.isEmpty()) {
+                    layout.rows[row].columns.flatMapLatest { columns ->
+                        if (columns.isEmpty()) {
                             flowOf(emptyList<DeviceSettingPreferenceModel>())
                         } else {
                             combine(
-                                settingIds.map { settingId ->
-                                    viewModel.getDeviceSetting(cachedDevice, settingId)
-                                }) {
-                                    it.toList()
+                                columns.map { column ->
+                                    viewModel.getDeviceSetting(cachedDevice, column.settingId)
                                 }
+                            ) {
+                                it.toList()
+                            }
                         }
                     }
                 }
                 .collectAsStateWithLifecycle(initialValue = listOf())
 
+        val highlighted by
+            remember(row) {
+                    layout.rows[row].columns.map { columns -> columns.any { it.highlighted } }
+                }
+                .collectAsStateWithLifecycle(initialValue = false)
+
         val settings = contents
+        AnimatedVisibility(
+            visible = settings.isNotEmpty(),
+            enter = expandVertically(expandFrom = Alignment.Top),
+            exit = shrinkVertically(shrinkTowards = Alignment.Top),
+        ) {
+            Box {
+                Box(
+                    modifier =
+                    Modifier.matchParentSize()
+                        .padding(16.dp, 0.dp, 8.dp, 0.dp)
+                        .background(
+                            color =
+                            if (highlighted) {
+                                MaterialTheme.colorScheme.primaryContainer
+                            } else {
+                                Color.Transparent
+                            },
+                            shape = RoundedCornerShape(28.dp),
+                        ),
+                ) {}
+                buildPreferences(settings)
+            }
+        }
+    }
+
+    @Composable
+    fun buildPreferences(settings: List<DeviceSettingPreferenceModel?>) {
         when (settings.size) {
             0 -> {}
             1 -> {
@@ -217,11 +271,18 @@
                 }
             }
             else -> {
-                if (!settings.all { it is DeviceSettingPreferenceModel.MultiTogglePreference }) {
+                if (
+                    !settings.all {
+                        it is DeviceSettingPreferenceModel.MultiTogglePreference
+                    }
+                ) {
                     return
                 }
                 buildMultiTogglePreference(
-                    settings.filterIsInstance<DeviceSettingPreferenceModel.MultiTogglePreference>())
+                    settings.filterIsInstance<
+                            DeviceSettingPreferenceModel.MultiTogglePreference
+                            >()
+                )
             }
         }
     }
@@ -243,11 +304,19 @@
                 override val onCheckedChange = { newChecked: Boolean ->
                     model.onCheckedChange(newChecked)
                 }
-                override val icon = @Composable { deviceSettingIcon(model.icon) }
+                override val icon: (@Composable () -> Unit)?
+                    get() {
+                        if (model.icon == null) {
+                            return null
+                        }
+                        return { deviceSettingIcon(model.icon) }
+                    }
             }
         if (model.onPrimaryClick != null) {
             TwoTargetSwitchPreference(
-                switchPrefModel, primaryOnClick = model.onPrimaryClick::invoke)
+                switchPrefModel,
+                primaryOnClick = model.onPrimaryClick::invoke,
+            )
         } else {
             SwitchPreference(switchPrefModel)
         }
@@ -263,8 +332,15 @@
                     model.onClick?.invoke()
                     Unit
                 }
-                override val icon = @Composable { deviceSettingIcon(model.icon) }
-            })
+                override val icon: (@Composable () -> Unit)?
+                    get() {
+                        if (model.icon == null) {
+                            return null
+                        }
+                        return { deviceSettingIcon(model.icon) }
+                    }
+            }
+        )
     }
 
     @Composable
@@ -281,11 +357,13 @@
                         .setDestination(DeviceDetailsMoreSettingsFragment::class.java.name)
                         .setSourceMetricsCategory(fragment.getMetricsCategory())
                         .setArguments(
-                            Bundle().apply { putString(KEY_DEVICE_ADDRESS, cachedDevice.address) })
+                            Bundle().apply { putString(KEY_DEVICE_ADDRESS, cachedDevice.address) }
+                        )
                         .launch()
                 }
                 override val icon = @Composable { deviceSettingIcon(null) }
-            })
+            }
+        )
     }
 
     @Composable
diff --git a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
index 1071adc..67a0ebc 100644
--- a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
+++ b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.settings.R
 import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
+import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutColumn
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutRow
 import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
 import com.android.settings.bluetooth.ui.model.FragmentTypeModel
@@ -36,7 +37,6 @@
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
@@ -51,7 +51,7 @@
     private val spatialAudioInteractor: SpatialAudioInteractor,
     private val cachedDevice: CachedBluetoothDevice,
     backgroundCoroutineContext: CoroutineContext,
-) : AndroidViewModel(application){
+) : AndroidViewModel(application) {
 
     private val items =
         viewModelScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
@@ -74,7 +74,7 @@
 
     fun getDeviceSetting(
         cachedDevice: CachedBluetoothDevice,
-        @DeviceSettingId settingId: Int
+        @DeviceSettingId settingId: Int,
     ): Flow<DeviceSettingPreferenceModel?> {
         if (settingId == DeviceSettingId.DEVICE_SETTING_ID_MORE_SETTINGS) {
             return flowOf(DeviceSettingPreferenceModel.MoreSettingsPreference(settingId))
@@ -98,16 +98,19 @@
                         checked = switchState?.checked ?: false,
                         onCheckedChange = { newState ->
                             updateState?.invoke(
-                                DeviceSettingStateModel.ActionSwitchPreferenceState(newState))
+                                DeviceSettingStateModel.ActionSwitchPreferenceState(newState)
+                            )
                         },
-                        onPrimaryClick = { intent?.let { application.startActivity(it) } })
+                        onPrimaryClick = { intent?.let { application.startActivity(it) } },
+                    )
                 } else {
                     DeviceSettingPreferenceModel.PlainPreference(
                         id = id,
                         title = title,
                         summary = summary,
                         icon = icon,
-                        onClick = { intent?.let { application.startActivity(it) } })
+                        onClick = { intent?.let { application.startActivity(it) } },
+                    )
                 }
             }
             is DeviceSettingModel.FooterPreference ->
@@ -116,9 +119,8 @@
                 DeviceSettingPreferenceModel.HelpPreference(
                     id = id,
                     icon = DeviceSettingIcon.ResourceIcon(R.drawable.ic_help),
-                    onClick = {
-                        application.startActivity(intent)
-                    })
+                    onClick = { application.startActivity(intent) },
+                )
             is DeviceSettingModel.MultiTogglePreference ->
                 DeviceSettingPreferenceModel.MultiTogglePreference(
                     id = id,
@@ -129,7 +131,8 @@
                     isAllowedChangingState = isAllowedChangingState,
                     onSelectedChange = { newState ->
                         updateState(DeviceSettingStateModel.MultiTogglePreferenceState(newState))
-                    })
+                    },
+                )
             is DeviceSettingModel.Unknown -> null
         }
     }
@@ -145,8 +148,8 @@
             configItems.map { idToDeviceSetting[it.settingId] ?: flowOf(null) }
         val positionToSettingIds =
             combine(configDeviceSetting) { settings ->
-                    val positionMapping = mutableMapOf<Int, List<Int>>()
-                    var multiToggleSettingIds: MutableList<Int>? = null
+                    val positionMapping = mutableMapOf<Int, List<DeviceSettingLayoutColumn>>()
+                    var multiToggleSettingIds: MutableList<DeviceSettingLayoutColumn>? = null
                     for (i in settings.indices) {
                         val configItem = configItems[i]
                         val setting = settings[i]
@@ -156,14 +159,31 @@
                         }
                         if (setting !is DeviceSettingPreferenceModel.MultiTogglePreference) {
                             multiToggleSettingIds = null
-                            positionMapping[i] = listOf(configItem.settingId)
+                            positionMapping[i] =
+                                listOf(
+                                    DeviceSettingLayoutColumn(
+                                        configItem.settingId,
+                                        configItem.highlighted,
+                                    )
+                                )
                             continue
                         }
 
                         if (multiToggleSettingIds != null) {
-                            multiToggleSettingIds.add(setting.id)
+                            multiToggleSettingIds.add(
+                                DeviceSettingLayoutColumn(
+                                    configItem.settingId,
+                                    configItem.highlighted,
+                                )
+                            )
                         } else {
-                            multiToggleSettingIds = mutableListOf(setting.id)
+                            multiToggleSettingIds =
+                                mutableListOf(
+                                    DeviceSettingLayoutColumn(
+                                        configItem.settingId,
+                                        configItem.highlighted,
+                                    )
+                                )
                             positionMapping[i] = multiToggleSettingIds
                         }
                     }
@@ -173,7 +193,8 @@
         return DeviceSettingLayout(
             configItems.indices.map { idx ->
                 DeviceSettingLayoutRow(positionToSettingIds.map { it[idx] ?: emptyList() })
-            })
+            }
+        )
     }
 
     class Factory(
@@ -186,9 +207,12 @@
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
             return BluetoothDeviceDetailsViewModel(
-                application, deviceSettingRepository, spatialAudioInteractor,
+                application,
+                deviceSettingRepository,
+                spatialAudioInteractor,
                 cachedDevice,
-                backgroundCoroutineContext)
+                backgroundCoroutineContext,
+            )
                 as T
         }
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
index b00f407..79cc56e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
@@ -44,7 +44,7 @@
     private static final String TAG = "AudioSharingLoadingDlg";
 
     private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message";
-    private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10);
+    private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15);
     private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message;
 
     private static String sMessage = "";
@@ -74,13 +74,15 @@
         }
         AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
         if (dialog != null) {
-            if (sMessage.equals(message)) {
-                Log.d(TAG, "Dialog is showing with same message, return.");
-                return;
-            } else {
-                Log.d(TAG, "Dialog is showing with different message, dismiss and reshow.");
-                dialog.dismiss();
+            if (!sMessage.equals(message)) {
+                Log.d(TAG, "Update dialog message.");
+                TextView messageView = dialog.findViewById(R.id.message);
+                if (messageView != null) {
+                    messageView.setText(message);
+                }
             }
+            Log.d(TAG, "Dialog is showing, return.");
+            return;
         }
         sMessage = message;
         Log.d(TAG, "Show up the loading dialog.");
@@ -113,8 +115,10 @@
     @NonNull
     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
         mHandler = new Handler(Looper.getMainLooper());
-        mHandler.postDelayed(() -> dismiss(), AUTO_DISMISS_MESSAGE_ID,
-                AUTO_DISMISS_TIME_THRESHOLD_MS);
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Auto dismiss dialog after timeout");
+            dismiss();
+        }, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
         Bundle args = requireArguments();
         String message = args.getString(BUNDLE_KEY_MESSAGE, "");
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -132,6 +136,7 @@
     public void onDismiss(@NonNull DialogInterface dialog) {
         super.onDismiss(dialog);
         if (mHandler != null) {
+            Log.d(TAG, "Dialog dismissed, remove auto dismiss task");
             mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
         }
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 2040694..c0f463d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -70,6 +70,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -112,12 +113,13 @@
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final OnAudioSharingStateChangedListener mListener;
     private Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
-    private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>();
+    @Nullable private AudioSharingDeviceItem mTargetActiveItem;
     private List<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
     @VisibleForTesting IntentFilter mIntentFilter;
     private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
     private AtomicInteger mIntentHandleStage =
             new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal());
+    private CopyOnWriteArrayList<BluetoothDevice> mSinksInAdding = new CopyOnWriteArrayList<>();
 
     @VisibleForTesting
     BroadcastReceiver mReceiver =
@@ -294,7 +296,16 @@
                 public void onReceiveStateChanged(
                         @NonNull BluetoothDevice sink,
                         int sourceId,
-                        @NonNull BluetoothLeBroadcastReceiveState state) {}
+                        @NonNull BluetoothLeBroadcastReceiveState state) {
+                    if (BluetoothUtils.isConnected(state)) {
+                        if (mSinksInAdding.contains(sink)) {
+                            mSinksInAdding.remove(sink);
+                        }
+                        dismissLoadingStateDialogIfNeeded();
+                        Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink
+                                + ", remaining sinks = " + mSinksInAdding);
+                    }
+                }
             };
 
     AudioSharingSwitchBarController(
@@ -506,17 +517,20 @@
                         mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false);
         // deviceItems is ordered. The active device is the first place if exits.
         mDeviceItemsForSharing = new ArrayList<>(deviceItems);
-        mTargetActiveSinks = new ArrayList<>();
+        mTargetActiveItem = null;
         if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) {
             // If active device exists for audio sharing, share to it
             // automatically once the broadcast is started.
-            mTargetActiveSinks =
-                    mGroupedConnectedDevices.getOrDefault(
-                            deviceItems.get(0).getGroupId(), ImmutableList.of());
+            mTargetActiveItem = deviceItems.get(0);
             mDeviceItemsForSharing.remove(0);
         }
         if (mBroadcast != null) {
             mBroadcast.startPrivateBroadcast();
+            mSinksInAdding.clear();
+            // TODO: use string res once finalized.
+            AudioSharingUtils.postOnMainThread(mContext,
+                    () -> AudioSharingLoadingStateDialogFragment.show(mFragment,
+                            "Starting audio stream..."));
             mMetricsFeatureProvider.action(
                     mContext,
                     SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON,
@@ -580,27 +594,30 @@
     }
 
     private void handleOnBroadcastReady() {
+        List<BluetoothDevice> targetActiveSinks = mTargetActiveItem == null ? ImmutableList.of()
+                : mGroupedConnectedDevices.getOrDefault(
+                        mTargetActiveItem.getGroupId(), ImmutableList.of());
         Pair<Integer, Object>[] eventData =
                 AudioSharingUtils.buildAudioSharingDialogEventData(
                         SettingsEnums.AUDIO_SHARING_SETTINGS,
                         SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE,
                         /* userTriggered= */ false,
-                        /* deviceCountInSharing= */ mTargetActiveSinks.isEmpty() ? 0 : 1,
+                        /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1,
                         /* candidateDeviceCount= */ mDeviceItemsForSharing.size());
-        if (!mTargetActiveSinks.isEmpty()) {
+        if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) {
             Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks.");
-            AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager);
+            addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName());
             mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
-            mTargetActiveSinks.clear();
+            mTargetActiveItem = null;
             if (mIntentHandleStage.compareAndSet(
                             StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
                             StartIntentHandleStage.HANDLED.ordinal())
                     && mDeviceItemsForSharing.size() == 1) {
                 Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
-                AudioSharingUtils.addSourceToTargetSinks(
-                        mGroupedConnectedDevices.getOrDefault(
-                                mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()),
-                        mBtManager);
+                AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
+                List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
+                        target.getGroupId(), ImmutableList.of());
+                addSourceToTargetSinks(targetSinks, target.getName());
                 cleanUp();
                 // TODO: Add metric for auto add by intent
                 return;
@@ -611,6 +628,7 @@
                 StartIntentHandleStage.HANDLED.ordinal());
         if (mFragment == null) {
             Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment.");
+            dismissLoadingStateDialogIfNeeded();
             cleanUp();
             return;
         }
@@ -622,15 +640,15 @@
                 new AudioSharingDialogFragment.DialogEventListener() {
                     @Override
                     public void onItemClick(@NonNull AudioSharingDeviceItem item) {
-                        AudioSharingUtils.addSourceToTargetSinks(
-                                mGroupedConnectedDevices.getOrDefault(
-                                        item.getGroupId(), ImmutableList.of()),
-                                mBtManager);
+                        List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
+                                item.getGroupId(), ImmutableList.of());
+                        addSourceToTargetSinks(targetSinks, item.getName());
                         cleanUp();
                     }
 
                     @Override
                     public void onCancelClick() {
+                        dismissLoadingStateDialogIfNeeded();
                         cleanUp();
                     }
                 };
@@ -700,6 +718,27 @@
                         });
     }
 
+    private void addSourceToTargetSinks(List<BluetoothDevice> targetActiveSinks,
+            @NonNull String sinkName) {
+        mSinksInAdding.addAll(targetActiveSinks);
+        AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager);
+        // TODO: move to res once finalized
+        String loadingMessage = "Sharing with " + sinkName + "...";
+        showLoadingStateDialog(loadingMessage);
+    }
+
+    private void showLoadingStateDialog(@NonNull String loadingMessage) {
+        AudioSharingUtils.postOnMainThread(mContext,
+                () -> AudioSharingLoadingStateDialogFragment.show(mFragment, loadingMessage));
+    }
+
+    private void dismissLoadingStateDialogIfNeeded() {
+        if (mSinksInAdding.isEmpty()) {
+            AudioSharingUtils.postOnMainThread(mContext,
+                    () -> AudioSharingLoadingStateDialogFragment.dismiss(mFragment));
+        }
+    }
+
     private void cleanUp() {
         mGroupedConnectedDevices.clear();
         mDeviceItemsForSharing.clear();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
index 939dd5c..48acf32 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
 import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -41,6 +43,7 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.settingslib.widget.ActionButtonsPreference;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
@@ -73,12 +76,18 @@
                         int sourceId,
                         BluetoothLeBroadcastReceiveState state) {
                     super.onReceiveStateChanged(sink, sourceId, state);
-                    if (AudioStreamsHelper.isConnected(state)) {
+                    boolean shouldUpdateButton =
+                            audioSharingHysteresisModeFix()
+                                    ? AudioStreamsHelper.hasSourcePresent(state)
+                                    : AudioStreamsHelper.isConnected(state);
+                    if (shouldUpdateButton) {
                         updateButton();
-                        mMetricsFeatureProvider.action(
-                                mContext,
-                                SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
-                                SOURCE_ORIGIN_REPOSITORY);
+                        if (AudioStreamsHelper.isConnected(state)) {
+                            mMetricsFeatureProvider.action(
+                                    mContext,
+                                    SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
+                                    SOURCE_ORIGIN_REPOSITORY);
+                        }
                     }
                 }
 
@@ -146,8 +155,13 @@
             Log.w(TAG, "updateButton(): preference is null!");
             return;
         }
+
+        List<BluetoothLeBroadcastReceiveState> sources =
+                audioSharingHysteresisModeFix()
+                        ? mAudioStreamsHelper.getAllPresentSources()
+                        : mAudioStreamsHelper.getAllConnectedSources();
         boolean isConnected =
-                mAudioStreamsHelper.getAllConnectedSources().stream()
+                sources.stream()
                         .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
                         .anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId);
 
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
index e1a178d..0ee93e7 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -16,6 +16,10 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
+import static java.util.stream.Collectors.toList;
+
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -48,6 +52,8 @@
     static final int AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY =
             R.string.audio_streams_listening_now;
 
+    static final int AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY = R.string.audio_streams_present_now;
+
     @VisibleForTesting static final String AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY = "";
     private static final String TAG = "AudioStreamHeaderController";
     private static final String KEY = "audio_stream_header";
@@ -80,6 +86,10 @@
                         updateSummary();
                         mAudioStreamsHelper.startMediaService(
                                 mContext, mBroadcastId, mBroadcastName);
+                    } else if (audioSharingHysteresisModeFix()
+                            && AudioStreamsHelper.hasSourcePresent(state)) {
+                        // if source present but not connected, only update the summary
+                        updateSummary();
                     }
                 }
             };
@@ -140,8 +150,27 @@
         var unused =
                 ThreadUtils.postOnBackgroundThread(
                         () -> {
+                            var connectedSourceList =
+                                    mAudioStreamsHelper.getAllPresentSources().stream()
+                                            .filter(
+                                                    state ->
+                                                            (state.getBroadcastId()
+                                                                    == mBroadcastId))
+                                            .collect(toList());
+
                             var latestSummary =
-                                    mAudioStreamsHelper.getAllConnectedSources().stream()
+                                    audioSharingHysteresisModeFix()
+                                            ? connectedSourceList.isEmpty()
+                                                    ? AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY
+                                                    : (connectedSourceList.stream()
+                                                                    .anyMatch(
+                                                                            AudioStreamsHelper
+                                                                                    ::isConnected)
+                                                            ? mContext.getString(
+                                                                    AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
+                                                            : mContext.getString(
+                                                                    AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY))
+                                            : mAudioStreamsHelper.getAllConnectedSources().stream()
                                                     .map(
                                                             BluetoothLeBroadcastReceiveState
                                                                     ::getBroadcastId)
@@ -149,9 +178,10 @@
                                                             connectedBroadcastId ->
                                                                     connectedBroadcastId
                                                                             == mBroadcastId)
-                                            ? mContext.getString(
-                                                    AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
-                                            : AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+                                                    ? mContext.getString(
+                                                            AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
+                                                    : AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+
                             ThreadUtils.postOnMainThread(
                                     () -> {
                                         if (mHeaderController != null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
index 758984f..458cfab 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
@@ -18,6 +18,8 @@
 
 import static android.text.Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
 
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
 import android.os.Handler;
 import android.os.Looper;
 import android.text.SpannableString;
@@ -94,8 +96,12 @@
                     }
                     preference.setIsConnected(
                             newState
-                                    == AudioStreamsProgressCategoryController.AudioStreamState
-                                            .SOURCE_ADDED);
+                                            == AudioStreamsProgressCategoryController
+                                                    .AudioStreamState.SOURCE_ADDED
+                                    || (audioSharingHysteresisModeFix()
+                                            && newState
+                                                    == AudioStreamsProgressCategoryController
+                                                            .AudioStreamState.SOURCE_PRESENT));
                     preference.setOnPreferenceClickListener(getOnClickListener(controller));
                 });
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index c219e0b..c0d9162 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -19,6 +19,7 @@
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_TITLE;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
 
 import static java.util.Collections.emptyList;
 
@@ -63,6 +64,12 @@
 
     private final @Nullable LocalBluetoothManager mBluetoothManager;
     private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+    // Referring to Broadcast Audio Scan Service 1.0
+    // Table 3.9: Broadcast Receive State characteristic format
+    // 0x00000000: 0b0 = Not synchronized to BIS_index[x]
+    // 0xFFFFFFFF: Failed to sync to BIG
+    private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L;
+    private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL;
 
     AudioStreamsHelper(@Nullable LocalBluetoothManager bluetoothManager) {
         mBluetoothManager = bluetoothManager;
@@ -144,6 +151,19 @@
                 .toList();
     }
 
+    /** Retrieves a list of all LE broadcast receive states from sinks with source present. */
+    @VisibleForTesting
+    public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
+        if (mLeBroadcastAssistant == null) {
+            Log.w(TAG, "getAllPresentSources(): LeBroadcastAssistant is null!");
+            return emptyList();
+        }
+        return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
+                .flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
+                .filter(AudioStreamsHelper::hasSourcePresent)
+                .toList();
+    }
+
     /** Retrieves LocalBluetoothLeBroadcastAssistant. */
     @VisibleForTesting
     @Nullable
@@ -153,7 +173,18 @@
 
     /** Checks the connectivity status based on the provided broadcast receive state. */
     public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
-        return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
+        return state.getBisSyncState().stream()
+                .anyMatch(
+                        bitmap ->
+                                (bitmap != BIS_SYNC_NOT_SYNC_TO_BIS
+                                        && bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG));
+    }
+
+    /** Checks the connectivity status based on the provided broadcast receive state. */
+    public static boolean hasSourcePresent(BluetoothLeBroadcastReceiveState state) {
+        // Referring to Broadcast Audio Scan Service 1.0
+        // All zero address means no source on the sink device
+        return !state.getSourceDevice().getAddress().equals("00:00:00:00:00:00");
     }
 
     static boolean isBadCode(BluetoothLeBroadcastReceiveState state) {
@@ -242,7 +273,8 @@
         List<BluetoothLeBroadcastReceiveState> sourceList =
                 assistant.getAllSources(cachedDevice.getDevice());
         if (!sourceList.isEmpty()
-                && sourceList.stream().anyMatch(AudioStreamsHelper::isConnected)) {
+                && (audioSharingHysteresisModeFix()
+                        || sourceList.stream().anyMatch(AudioStreamsHelper::isConnected))) {
             Log.d(
                     TAG,
                     "Lead device has connected broadcast source, device = "
@@ -253,7 +285,9 @@
         for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
             List<BluetoothLeBroadcastReceiveState> list =
                     assistant.getAllSources(device.getDevice());
-            if (!list.isEmpty() && list.stream().anyMatch(AudioStreamsHelper::isConnected)) {
+            if (!list.isEmpty()
+                    && (audioSharingHysteresisModeFix()
+                            || list.stream().anyMatch(AudioStreamsHelper::isConnected))) {
                 Log.d(
                         TAG,
                         "Member device has connected broadcast source, device = "
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
index 3370d8d..b379d4e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -39,6 +41,9 @@
             mCategoryController.handleSourceConnected(state);
         } else if (AudioStreamsHelper.isBadCode(state)) {
             mCategoryController.handleSourceConnectBadCode(state);
+        } else if (audioSharingHysteresisModeFix() && AudioStreamsHelper.hasSourcePresent(state)) {
+            // Keep this check as the last, source might also present in above states
+            mCategoryController.handleSourcePresent(state);
         }
     }
 
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 9bbf135..7ab5882 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
+
 import static java.util.Collections.emptyList;
 
 import android.app.AlertDialog;
@@ -48,6 +50,7 @@
 import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.Comparator;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -95,9 +98,14 @@
     private final Comparator<AudioStreamPreference> mComparator =
             Comparator.<AudioStreamPreference, Boolean>comparing(
                             p ->
-                                    p.getAudioStreamState()
-                                            == AudioStreamsProgressCategoryController
-                                                    .AudioStreamState.SOURCE_ADDED)
+                                    (p.getAudioStreamState()
+                                                    == AudioStreamsProgressCategoryController
+                                                            .AudioStreamState.SOURCE_ADDED
+                                            || (audioSharingHysteresisModeFix()
+                                                    && p.getAudioStreamState()
+                                                            == AudioStreamsProgressCategoryController
+                                                                    .AudioStreamState
+                                                                    .SOURCE_PRESENT)))
                     .thenComparingInt(AudioStreamPreference::getAudioStreamRssi)
                     .reversed();
 
@@ -113,6 +121,8 @@
         ADD_SOURCE_BAD_CODE,
         // When addSource result in other bad state.
         ADD_SOURCE_FAILED,
+        // Source is present on sink.
+        SOURCE_PRESENT,
         // Source is added to active sink.
         SOURCE_ADDED,
     }
@@ -243,10 +253,13 @@
                                 existingPreference, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
                     } else {
                         // A preference with source founded existed either because it's already
-                        // connected (SOURCE_ADDED). Any other reason is unexpected. We update the
-                        // preference with this source and won't change it's state.
+                        // connected (SOURCE_ADDED) or present (SOURCE_PRESENT). Any other reason
+                        // is unexpected. We update the preference with this source and won't
+                        // change it's state.
                         existingPreference.setAudioStreamMetadata(source);
-                        if (fromState != AudioStreamState.SOURCE_ADDED) {
+                        if (fromState != AudioStreamState.SOURCE_ADDED
+                                && (!audioSharingHysteresisModeFix()
+                                        || fromState != AudioStreamState.SOURCE_PRESENT)) {
                             Log.w(
                                     TAG,
                                     "handleSourceFound(): unexpected state : "
@@ -346,10 +359,14 @@
         for (var entry : mBroadcastIdToPreferenceMap.entrySet()) {
             var preference = entry.getValue();
 
-            // Look for preference has SOURCE_ADDED state, re-check if they are still connected. If
+            // Look for preference has SOURCE_ADDED or SOURCE_PRESENT state, re-check if they are
+            // still connected. If
             // not, means the source is removed from the sink, we move back the preference to SYNCED
             // state.
-            if (preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
+            if ((preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
+                            || (audioSharingHysteresisModeFix()
+                                    && preference.getAudioStreamState()
+                                            == AudioStreamState.SOURCE_PRESENT))
                     && mAudioStreamsHelper.getAllConnectedSources().stream()
                             .noneMatch(
                                     connected ->
@@ -383,6 +400,7 @@
         if (!AudioStreamsHelper.isConnected(receiveState)) {
             return;
         }
+
         var broadcastIdConnected = receiveState.getBroadcastId();
         if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
             // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the
@@ -455,6 +473,58 @@
                 });
     }
 
+    // Find preference by receiveState and decide next state.
+    // Expect one preference existed, move to SOURCE_PRESENT
+    void handleSourcePresent(BluetoothLeBroadcastReceiveState receiveState) {
+        if (DEBUG) {
+            Log.d(TAG, "handleSourcePresent()");
+        }
+        if (!AudioStreamsHelper.hasSourcePresent(receiveState)) {
+            return;
+        }
+
+        var broadcastIdConnected = receiveState.getBroadcastId();
+        if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
+            // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the
+            // connected source receiveState.
+            if (DEBUG) {
+                Log.d(
+                        TAG,
+                        "handleSourcePresent() : processing mSourceFromQrCode with broadcastId"
+                                + " unset");
+            }
+            boolean updated =
+                    maybeUpdateId(
+                            AudioStreamsHelper.getBroadcastName(receiveState),
+                            receiveState.getBroadcastId());
+            if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) {
+                var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID);
+                mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference);
+            }
+        }
+
+        mBroadcastIdToPreferenceMap.compute(
+                broadcastIdConnected,
+                (k, existingPreference) -> {
+                    if (existingPreference == null) {
+                        // No existing preference for this source even if it's already connected,
+                        // add one and set initial state to SOURCE_PRESENT. This could happen
+                        // because
+                        // we retrieves the connected source during onStart() from
+                        // AudioStreamsHelper#getAllPresentSources() even before the source is
+                        // founded by scanning.
+                        return addNewPreference(receiveState, AudioStreamState.SOURCE_PRESENT);
+                    }
+                    if (existingPreference.getAudioStreamState() == AudioStreamState.WAIT_FOR_SYNC
+                            && existingPreference.getAudioStreamBroadcastId() == UNSET_BROADCAST_ID
+                            && mSourceFromQrCode != null) {
+                        existingPreference.setAudioStreamMetadata(mSourceFromQrCode);
+                    }
+                    moveToState(existingPreference, AudioStreamState.SOURCE_PRESENT);
+                    return existingPreference;
+                });
+    }
+
     // Find preference by metadata and decide next state.
     // Expect one preference existed, move to ADD_SOURCE_WAIT_FOR_RESPONSE
     void handleSourceAddRequest(
@@ -530,9 +600,23 @@
                     // Handle QR code scan, display currently connected streams then start scanning
                     // sequentially
                     handleSourceFromQrCodeIfExists();
-                    mAudioStreamsHelper
-                            .getAllConnectedSources()
-                            .forEach(this::handleSourceConnected);
+                    if (audioSharingHysteresisModeFix()) {
+                        // With hysteresis mode, we prioritize showing connected sources first.
+                        // If no connected sources are found, we then show present sources.
+                        List<BluetoothLeBroadcastReceiveState> sources =
+                                mAudioStreamsHelper.getAllConnectedSources();
+                        if (!sources.isEmpty()) {
+                            sources.forEach(this::handleSourceConnected);
+                        } else {
+                            mAudioStreamsHelper
+                                    .getAllPresentSources()
+                                    .forEach(this::handleSourcePresent);
+                        }
+                    } else {
+                        mAudioStreamsHelper
+                                .getAllConnectedSources()
+                                .forEach(this::handleSourceConnected);
+                    }
                     mLeBroadcastAssistant.startSearchingForSources(emptyList());
                     mMediaControlHelper.start();
                 });
@@ -581,6 +665,7 @@
                             AddSourceWaitForResponseState.getInstance();
                     case ADD_SOURCE_BAD_CODE -> AddSourceBadCodeState.getInstance();
                     case ADD_SOURCE_FAILED -> AddSourceFailedState.getInstance();
+                    case SOURCE_PRESENT -> SourcePresentState.getInstance();
                     case SOURCE_ADDED -> SourceAddedState.getInstance();
                     default -> throw new IllegalArgumentException("Unsupported state: " + state);
                 };
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentState.java
new file mode 100644
index 0000000..1e724f1
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentState.java
@@ -0,0 +1,87 @@
+/*
+ * 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.audiosharing.audiostreams;
+
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.dashboard.DashboardFragment;
+
+class SourcePresentState extends AudioStreamStateHandler {
+    @VisibleForTesting
+    static final int AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY = R.string.audio_streams_present_now;
+
+    @Nullable private static SourcePresentState sInstance = null;
+
+    SourcePresentState() {}
+
+    static SourcePresentState getInstance() {
+        if (sInstance == null) {
+            sInstance = new SourcePresentState();
+        }
+        return sInstance;
+    }
+
+    @Override
+    void performAction(
+            AudioStreamPreference preference,
+            AudioStreamsProgressCategoryController controller,
+            AudioStreamsHelper helper) {
+        // nothing to do
+    }
+
+    @Override
+    int getSummary() {
+        return AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY;
+    }
+
+    @Override
+    Preference.OnPreferenceClickListener getOnClickListener(
+            AudioStreamsProgressCategoryController controller) {
+        return preference -> {
+            var p = (AudioStreamPreference) preference;
+            Bundle broadcast = new Bundle();
+            broadcast.putString(
+                    AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) p.getTitle());
+            broadcast.putInt(
+                    AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId());
+
+            new SubSettingLauncher(p.getContext())
+                    .setTitleRes(R.string.audio_streams_detail_page_title)
+                    .setDestination(AudioStreamDetailsFragment.class.getName())
+                    .setSourceMetricsCategory(
+                            !(controller.getFragment() instanceof DashboardFragment)
+                                    ? SettingsEnums.PAGE_UNKNOWN
+                                    : ((DashboardFragment) controller.getFragment())
+                                            .getMetricsCategory())
+                    .setArguments(broadcast)
+                    .launch();
+            return true;
+        };
+    }
+
+    @Override
+    AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() {
+        return AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT;
+    }
+}
diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
index 4d871d4..9b03e9b 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -37,7 +37,6 @@
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
 
 /**
  * Instrumented fragment that logs visibility state.
diff --git a/src/com/android/settings/core/ObservablePreferenceFragment.java b/src/com/android/settings/core/ObservablePreferenceFragment.java
new file mode 100644
index 0000000..997317d
--- /dev/null
+++ b/src/com/android/settings/core/ObservablePreferenceFragment.java
@@ -0,0 +1,137 @@
+/*
+ * 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.core;
+
+
+import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
+import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.annotation.CallSuper;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.preference.PreferenceFragment;
+
+/**
+ * Preference fragment that has hooks to observe fragment lifecycle events.
+ */
+public abstract class ObservablePreferenceFragment extends PreferenceFragment
+        implements LifecycleOwner {
+
+    private final Lifecycle mLifecycle = new Lifecycle(this);
+
+    public Lifecycle getSettingsLifecycle() {
+        return mLifecycle;
+    }
+
+    @CallSuper
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mLifecycle.onAttach(context);
+    }
+
+    @CallSuper
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mLifecycle.onCreate(savedInstanceState);
+        mLifecycle.handleLifecycleEvent(ON_CREATE);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+        mLifecycle.setPreferenceScreen(preferenceScreen);
+        super.setPreferenceScreen(preferenceScreen);
+    }
+
+    @CallSuper
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        mLifecycle.onSaveInstanceState(outState);
+    }
+
+    @CallSuper
+    @Override
+    public void onStart() {
+        mLifecycle.handleLifecycleEvent(ON_START);
+        super.onStart();
+    }
+
+    @CallSuper
+    @Override
+    public void onResume() {
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
+        super.onResume();
+    }
+
+    @CallSuper
+    @Override
+    public void onPause() {
+        mLifecycle.handleLifecycleEvent(ON_PAUSE);
+        super.onPause();
+    }
+
+    @CallSuper
+    @Override
+    public void onStop() {
+        mLifecycle.handleLifecycleEvent(ON_STOP);
+        super.onStop();
+    }
+
+    @CallSuper
+    @Override
+    public void onDestroy() {
+        mLifecycle.handleLifecycleEvent(ON_DESTROY);
+        super.onDestroy();
+    }
+
+    @CallSuper
+    @Override
+    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+        mLifecycle.onCreateOptionsMenu(menu, inflater);
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    @CallSuper
+    @Override
+    public void onPrepareOptionsMenu(final Menu menu) {
+        mLifecycle.onPrepareOptionsMenu(menu);
+        super.onPrepareOptionsMenu(menu);
+    }
+
+    @CallSuper
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem menuItem) {
+        boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem);
+        if (!lifecycleHandled) {
+            return super.onOptionsItemSelected(menuItem);
+        }
+        return lifecycleHandled;
+    }
+}
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index 09dd1dd..220856a 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -55,7 +55,6 @@
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
-import com.android.settingslib.metadata.PreferenceScreenRegistry;
 import com.android.settingslib.search.Indexable;
 
 import java.util.ArrayList;
@@ -101,8 +100,7 @@
         mDashboardFeatureProvider =
                 FeatureFactory.getFeatureFactory().getDashboardFeatureProvider();
 
-        if (!usePreferenceScreenMetadata() || PreferenceScreenRegistry.INSTANCE.get(
-                getPreferenceScreenBindingKey(context)) == null) {
+        if (!isCatalystEnabled()) {
             // Load preference controllers from code
             final List<AbstractPreferenceController> controllersFromCode =
                     createPreferenceControllers(context);
@@ -378,7 +376,7 @@
             return;
         }
         PreferenceScreen screen;
-        if (usePreferenceScreenMetadata()) {
+        if (isCatalystEnabled()) {
             screen = createPreferenceScreen();
             setPreferenceScreen(screen);
             requireActivity().setTitle(screen.getTitle());
@@ -390,17 +388,13 @@
         displayResourceTilesToScreen(screen);
     }
 
-    @Override
-    protected final boolean usePreferenceScreenMetadata() {
-        return settingsCatalyst() && enableCatalyst();
-    }
-
-    /**
-     * Returns if settings catalyst should be enabled (e.g. check trunk stable flag) on current
-     * screen.
-     */
-    protected boolean enableCatalyst() {
-        return false;
+    /** Returns if catalyst is enabled on current screen. */
+    protected final boolean isCatalystEnabled() {
+        if (!settingsCatalyst()) {
+            return false;
+        }
+        Context context = getContext();
+        return context != null ? getPreferenceScreenCreator(context) != null : false;
     }
 
     /**
diff --git a/src/com/android/settings/datausage/DataUsageSummary.java b/src/com/android/settings/datausage/DataUsageSummary.java
index 5681c92..dfb3508 100644
--- a/src/com/android/settings/datausage/DataUsageSummary.java
+++ b/src/com/android/settings/datausage/DataUsageSummary.java
@@ -180,14 +180,16 @@
     void addWifiSection() {
         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
                 inflatePreferences(R.xml.data_usage_wifi);
-        category.setTemplate(new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build(), 0);
+        category.setTemplate(new NetworkTemplate.Builder(NetworkTemplate.MATCH_WIFI).build(),
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
     }
 
     private void addEthernetSection() {
         TemplatePreferenceCategory category = (TemplatePreferenceCategory)
                 inflatePreferences(R.xml.data_usage_ethernet);
         category.setTemplate(
-                new NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build(), 0);
+                new NetworkTemplate.Builder(NetworkTemplate.MATCH_ETHERNET).build(),
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
     }
 
     private Preference inflatePreferences(int resId) {
diff --git a/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java b/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java
new file mode 100644
index 0000000..2324c5e
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.inputmethod;
+
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse;
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Settings for pointer and touchpad. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class PointerColorCustomizationFragment extends DashboardFragment {
+
+    private static final String TAG = "PointerColorCustomizationFragment";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.ACCESSIBILITY_POINTER_COLOR_CUSTOMIZATION;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.accessibility_pointer_color_customization;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.accessibility_pointer_color_customization) {
+                @Override
+                protected boolean isPageSearchEnabled(Context context) {
+                    return isTouchpad() || isMouse();
+                }
+            };
+}
diff --git a/src/com/android/settings/inputmethod/PointerTouchpadFragment.java b/src/com/android/settings/inputmethod/PointerTouchpadFragment.java
new file mode 100644
index 0000000..fc069ca
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerTouchpadFragment.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.inputmethod;
+
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse;
+import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Accessibility settings for pointer and touchpad. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class PointerTouchpadFragment extends DashboardFragment {
+
+    private static final String TAG = "PointerTouchpadFragment";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.accessibility_pointer_and_touchpad;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider(R.xml.accessibility_pointer_and_touchpad) {
+                @Override
+                protected boolean isPageSearchEnabled(Context context) {
+                    return isTouchpad() || isMouse();
+                }
+            };
+}
diff --git a/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java b/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java
new file mode 100644
index 0000000..b6ea063
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.inputmethod;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.core.BasePreferenceController;
+
+/** Controller that shows and updates the pointer touchpad preference. */
+public class PointerTouchpadPreferenceController extends BasePreferenceController {
+
+    public PointerTouchpadPreferenceController(@NonNull Context context,
+            @NonNull String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        boolean isTouchpad = NewKeyboardSettingsUtils.isTouchpad();
+        boolean isMouse = NewKeyboardSettingsUtils.isMouse();
+        return (isTouchpad || isMouse) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.kt b/src/com/android/settings/network/MobileNetworkListFragment.kt
index bb88330..d110779 100644
--- a/src/com/android/settings/network/MobileNetworkListFragment.kt
+++ b/src/com/android/settings/network/MobileNetworkListFragment.kt
@@ -27,13 +27,13 @@
 import com.android.settings.SettingsPreferenceFragment
 import com.android.settings.dashboard.DashboardFragment
 import com.android.settings.flags.Flags
+import com.android.settings.network.telephony.SimRepository
 import com.android.settings.network.telephony.euicc.EuiccRepository
 import com.android.settings.search.BaseSearchIndexProvider
 import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
 import com.android.settings.spa.network.NetworkCellularGroupProvider
 import com.android.settingslib.search.SearchIndexable
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
-import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
 
 @SearchIndexable(forTarget = SearchIndexable.ALL and SearchIndexable.ARC.inv())
@@ -85,10 +85,11 @@
         val SEARCH_INDEX_DATA_PROVIDER = SearchIndexProvider()
 
         @VisibleForTesting
-        class SearchIndexProvider : BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
+        class SearchIndexProvider(
+            private val simRepositoryFactory: (Context) -> SimRepository = ::SimRepository
+        ) : BaseSearchIndexProvider(R.xml.network_provider_sims_list) {
             public override fun isPageSearchEnabled(context: Context): Boolean =
-                SubscriptionUtil.isSimHardwareVisible(context) &&
-                    context.userManager.isAdminUser
+                simRepositoryFactory(context).showMobileNetworkPage()
         }
     }
 }
diff --git a/src/com/android/settings/network/MobileNetworkPreferenceController.java b/src/com/android/settings/network/MobileNetworkPreferenceController.java
deleted file mode 100644
index b49613a..0000000
--- a/src/com/android/settings/network/MobileNetworkPreferenceController.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settings.network;
-
-import static android.os.UserHandle.myUserId;
-import static android.os.UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS;
-
-import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
-
-import static androidx.lifecycle.Lifecycle.Event;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.OnLifecycleEvent;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.network.telephony.MobileNetworkUtils;
-import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.Utils;
-import com.android.settingslib.core.AbstractPreferenceController;
-
-public class MobileNetworkPreferenceController extends AbstractPreferenceController
-        implements PreferenceControllerMixin, LifecycleObserver {
-
-    @VisibleForTesting
-    static final String KEY_MOBILE_NETWORK_SETTINGS = "mobile_network_settings";
-
-    private final boolean mIsSecondaryUser;
-    private final TelephonyManager mTelephonyManager;
-    private final UserManager mUserManager;
-    private Preference mPreference;
-    @VisibleForTesting
-    MobileNetworkTelephonyCallback mTelephonyCallback;
-
-    private BroadcastReceiver mAirplanModeChangedReceiver;
-
-    public MobileNetworkPreferenceController(Context context) {
-        super(context);
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        mIsSecondaryUser = !mUserManager.isAdminUser();
-
-        mAirplanModeChangedReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                updateState(mPreference);
-            }
-        };
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return !isUserRestricted() && !Utils.isWifiOnly(mContext);
-    }
-
-    public boolean isUserRestricted() {
-        return mIsSecondaryUser ||
-                RestrictedLockUtilsInternal.hasBaseUserRestriction(
-                        mContext,
-                        DISALLOW_CONFIG_MOBILE_NETWORKS,
-                        myUserId());
-    }
-
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        mPreference = screen.findPreference(getPreferenceKey());
-    }
-
-    @Override
-    public String getPreferenceKey() {
-        return KEY_MOBILE_NETWORK_SETTINGS;
-    }
-
-    class MobileNetworkTelephonyCallback extends TelephonyCallback implements
-            TelephonyCallback.ServiceStateListener {
-        @Override
-        public void onServiceStateChanged(ServiceState serviceState) {
-            updateState(mPreference);
-        }
-    }
-
-    @OnLifecycleEvent(Event.ON_START)
-    public void onStart() {
-        if (isAvailable()) {
-            if (mTelephonyCallback == null) {
-                mTelephonyCallback = new MobileNetworkTelephonyCallback();
-            }
-            mTelephonyManager.registerTelephonyCallback(
-                    mContext.getMainExecutor(), mTelephonyCallback);
-        }
-        if (mAirplanModeChangedReceiver != null) {
-            mContext.registerReceiver(mAirplanModeChangedReceiver,
-                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
-        }
-    }
-
-    @OnLifecycleEvent(Event.ON_STOP)
-    public void onStop() {
-        if (mTelephonyCallback != null) {
-            mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
-        }
-        if (mAirplanModeChangedReceiver != null) {
-            mContext.unregisterReceiver(mAirplanModeChangedReceiver);
-        }
-    }
-
-    @Override
-    public void updateState(Preference preference) {
-        super.updateState(preference);
-
-        if (preference instanceof RestrictedPreference &&
-            ((RestrictedPreference) preference).isDisabledByAdmin()) {
-                return;
-        }
-        preference.setEnabled(Settings.Global.getInt(
-            mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 0);
-    }
-
-    @Override
-    public boolean handlePreferenceTreeClick(Preference preference) {
-        if (KEY_MOBILE_NETWORK_SETTINGS.equals(preference.getKey())) {
-            final Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
-            intent.setPackage(SETTINGS_PACKAGE_NAME);
-            mContext.startActivity(intent);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public CharSequence getSummary() {
-        return MobileNetworkUtils.getCurrentCarrierNameForDisplay(mContext);
-    }
-}
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java
index 9bf6915..45d475f 100644
--- a/src/com/android/settings/network/MobileNetworkSummaryController.java
+++ b/src/com/android/settings/network/MobileNetworkSummaryController.java
@@ -21,7 +21,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.os.UserManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.euicc.EuiccManager;
 
@@ -35,10 +34,10 @@
 import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.network.telephony.SimRepository;
 import com.android.settings.network.telephony.euicc.EuiccRepository;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.Utils;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
@@ -56,7 +55,6 @@
     private static final String KEY = "mobile_network_list";
 
     private final MetricsFeatureProvider mMetricsFeatureProvider;
-    private UserManager mUserManager;
     private RestrictedPreference mPreference;
 
     private MobileNetworkRepository mMobileNetworkRepository;
@@ -85,7 +83,6 @@
             LifecycleOwner lifecycleOwner) {
         super(context);
         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-        mUserManager = context.getSystemService(UserManager.class);
         mLifecycleOwner = lifecycleOwner;
         mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
         mIsAirplaneModeOn = mMobileNetworkRepository.isAirplaneModeOn();
@@ -185,8 +182,7 @@
 
     @Override
     public boolean isAvailable() {
-        return SubscriptionUtil.isSimHardwareVisible(mContext) &&
-                !Utils.isWifiOnly(mContext) && mUserManager.isAdminUser();
+        return new SimRepository(mContext).showMobileNetworkPage();
     }
 
     @Override
diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
index df3b8ba..fe50522 100644
--- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
+++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
@@ -20,7 +20,7 @@
 import android.telephony.SubscriptionManager
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.android.settings.network.telephony.getSelectableSubscriptionInfoList
+import com.android.settings.network.telephony.SubscriptionRepository
 import com.android.settings.network.telephony.subscriptionsChangedFlow
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.SharingStarted
@@ -45,7 +45,8 @@
      * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's
      * getAvailableSubscriptionInfoList
      */
-    val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
-        application.getSelectableSubscriptionInfoList()
-    }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
+    val selectableSubscriptionInfoListFlow =
+        SubscriptionRepository(application)
+            .selectableSubscriptionInfoListFlow()
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
 }
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index fcf1599..aca2fc3 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -52,7 +52,7 @@
 import com.android.settings.network.helper.SubscriptionAnnotation;
 import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
 import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
-import com.android.settings.network.telephony.SubscriptionRepositoryKt;
+import com.android.settings.network.telephony.SubscriptionRepository;
 import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
 
 import java.util.ArrayList;
@@ -508,7 +508,7 @@
      * @return list of user selectable subscriptions.
      */
     public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
-        return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context);
+        return new SubscriptionRepository(context).getSelectableSubscriptionInfoList();
     }
 
     /**
diff --git a/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java
deleted file mode 100644
index a5c19ad..0000000
--- a/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import android.content.Context;
-import android.text.BidiFormatter;
-
-import com.android.settings.R;
-import com.android.settings.Utils;
-import com.android.settings.activityembedding.ActivityEmbeddingUtils;
-import com.android.settings.core.BasePreferenceController;
-
-public class TopLevelNetworkEntryPreferenceController extends BasePreferenceController {
-
-    private final MobileNetworkPreferenceController mMobileNetworkPreferenceController;
-
-    public TopLevelNetworkEntryPreferenceController(Context context, String preferenceKey) {
-        super(context, preferenceKey);
-        mMobileNetworkPreferenceController = new MobileNetworkPreferenceController(mContext);
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        // TODO(b/281597506): Update the ActivityEmbeddingUtils.isEmbeddingActivityEnabled
-        //   while getting the new API.
-        return (Utils.isDemoUser(mContext)
-            && !ActivityEmbeddingUtils.isEmbeddingActivityEnabled(mContext))
-                ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
-    }
-
-    @Override
-    public CharSequence getSummary() {
-        if (mMobileNetworkPreferenceController.isAvailable()) {
-            return BidiFormatter.getInstance()
-                    .unicodeWrap(mContext.getString(R.string.network_dashboard_summary_mobile));
-        } else {
-            return BidiFormatter.getInstance()
-                    .unicodeWrap(mContext.getString(R.string.network_dashboard_summary_no_mobile));
-        }
-    }
-}
diff --git a/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.kt b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.kt
new file mode 100644
index 0000000..1722f6a
--- /dev/null
+++ b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.text.BidiFormatter
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.activityembedding.ActivityEmbeddingUtils
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.network.telephony.SimRepository
+
+class TopLevelNetworkEntryPreferenceController
+@JvmOverloads
+constructor(
+    context: Context,
+    preferenceKey: String,
+    private val simRepository: SimRepository = SimRepository(context),
+    private val isDemoUser: () -> Boolean = { Utils.isDemoUser(context) },
+    private val isEmbeddingActivityEnabled: () -> Boolean = {
+        ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)
+    },
+) : BasePreferenceController(context, preferenceKey) {
+
+    override fun getAvailabilityStatus(): Int {
+        // TODO(b/281597506): Update the ActivityEmbeddingUtils.isEmbeddingActivityEnabled
+        //                    while getting the new API.
+        return if (isDemoUser() && !isEmbeddingActivityEnabled()) {
+            UNSUPPORTED_ON_DEVICE
+        } else {
+            AVAILABLE
+        }
+    }
+
+    override fun getSummary(): CharSequence {
+        val summaryResId =
+            if (simRepository.showMobileNetworkPage()) {
+                R.string.network_dashboard_summary_mobile
+            } else {
+                R.string.network_dashboard_summary_no_mobile
+            }
+        return BidiFormatter.getInstance().unicodeWrap(mContext.getString(summaryResId))
+    }
+}
diff --git a/src/com/android/settings/network/telephony/SimRepository.kt b/src/com/android/settings/network/telephony/SimRepository.kt
new file mode 100644
index 0000000..ed3c8aa
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SimRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.network.telephony
+
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+
+class SimRepository(context: Context) {
+    private val packageManager = context.packageManager
+    private val userManager = context.userManager
+
+    /** Gets whether we show mobile network settings page to the current user. */
+    fun showMobileNetworkPage(): Boolean =
+        packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && userManager.isAdminUser
+}
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 26ea9b3..6b5b4cb 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -42,13 +42,48 @@
 class SubscriptionRepository(private val context: Context) {
     private val subscriptionManager = context.requireSubscriptionManager()
 
+    /** A cold flow of a list of subscriptions that are available and visible to the user. */
+    fun selectableSubscriptionInfoListFlow(): Flow<List<SubscriptionInfo>> =
+        context
+            .subscriptionsChangedFlow()
+            .map { getSelectableSubscriptionInfoList() }
+            .conflate()
+            .flowOn(Dispatchers.Default)
+
     /**
      * Return a list of subscriptions that are available and visible to the user.
      *
      * @return list of user selectable subscriptions.
      */
-    fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> =
-        context.getSelectableSubscriptionInfoList()
+    fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
+        val availableList =
+            subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
+        val visibleList =
+            availableList.filter { subInfo ->
+                // Opportunistic subscriptions are considered invisible to users so they should
+                // never be returned.
+                SubscriptionUtil.isSubscriptionVisible(subscriptionManager, context, subInfo)
+            }
+        return visibleList
+            .groupBy { it.groupUuid }
+            .flatMap { (groupUuid, subInfos) ->
+                if (groupUuid == null) {
+                    subInfos
+                } else {
+                    // Multiple subscriptions in a group should only have one representative.
+                    // It should be the current active primary subscription if any, or the primary
+                    // subscription with minimum subscription id.
+                    subInfos
+                        .filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
+                        .ifEmpty { subInfos.sortedBy { it.subscriptionId } }
+                        .take(1)
+                }
+            }
+            // Matching the sorting order in
+            // SubscriptionManagerService.getAvailableSubscriptionInfoList
+            .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
+            .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
+    }
 
     /** Flow of whether the subscription visible for the given [subId]. */
     fun isSubscriptionVisibleFlow(subId: Int): Flow<Boolean> {
@@ -154,38 +189,6 @@
 fun Context.subscriptionsChangedFlow(): Flow<Unit> =
     SubscriptionRepository(this).subscriptionsChangedFlow()
 
-/**
- * Return a list of subscriptions that are available and visible to the user.
- *
- * @return list of user selectable subscriptions.
- */
-fun Context.getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
-    val subscriptionManager = requireSubscriptionManager()
-    val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList()
-    val visibleList = availableList.filter { subInfo ->
-        // Opportunistic subscriptions are considered invisible
-        // to users so they should never be returned.
-        SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo)
-    }
-    return visibleList
-        .groupBy { it.groupUuid }
-        .flatMap { (groupUuid, subInfos) ->
-            if (groupUuid == null) {
-                subInfos
-            } else {
-                // Multiple subscriptions in a group should only have one representative.
-                // It should be the current active primary subscription if any, or the primary
-                // subscription with minimum subscription id.
-                subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX }
-                    .ifEmpty { subInfos.sortedBy { it.subscriptionId } }
-                    .take(1)
-            }
-        }
-        // Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList
-        .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId }))
-        .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") }
-}
-
 /** Subscription with invalid sim slot index has lowest sort order. */
 private val SubscriptionInfo.sortableSimSlotIndex: Int
     get() = if (simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java
index 68cc167..9355b1b 100644
--- a/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java
@@ -58,6 +58,5 @@
             conditionId -> saveMode(mode -> {
                 mode.setCustomModeConditionId(mContext, conditionId);
                 return mode;
-                // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen?
             });
 }
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index 37772b3..89cb662 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -142,9 +142,6 @@
                 @Override
                 public List<String> getNonIndexableKeys(Context context) {
                     final List<String> keys = super.getNonIndexableKeys(context);
-                    // TODO: b/332937523 - determine if this should be removed once the preference
-                    //                     controller adds dynamic data to index
-                    keys.add(ZenModesListPreferenceController.KEY);
                     return keys;
                 }
 
diff --git a/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java
index 9332c9b..82f0816 100644
--- a/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceController.java
@@ -76,7 +76,7 @@
 
             // DND turned on by an automatic rule with deprecated zen mode
             for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
-                if (automaticRule.isAutomaticActive() && isDeprecatedZenMode(
+                if (automaticRule.isActive() && isDeprecatedZenMode(
                         automaticRule.zenMode)) {
                     ComponentName component = automaticRule.component;
                     if (component != null) {
diff --git a/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java
index 6a57441..4781b36 100644
--- a/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceController.java
@@ -153,7 +153,7 @@
 
         // DND turned on by an automatic rule
         for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
-            if (automaticRule.isAutomaticActive()) {
+            if (automaticRule.isActive()) {
                 // set footer if 3rd party rule
                 if (!mZenModeConfigWrapper.isTimeRule(automaticRule.conditionId)) {
                     return mContext.getString(R.string.zen_mode_settings_dnd_automatic_rule,
@@ -180,7 +180,7 @@
         }
 
         for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
-            if (automaticRule.isAutomaticActive()) {
+            if (automaticRule.isActive()) {
                 zenRules.add(automaticRule);
             }
         }
diff --git a/src/com/android/settings/security/OwnerInfoPreferenceController.java b/src/com/android/settings/security/OwnerInfoPreferenceController.java
index 2483016..67dbbc1 100644
--- a/src/com/android/settings/security/OwnerInfoPreferenceController.java
+++ b/src/com/android/settings/security/OwnerInfoPreferenceController.java
@@ -24,6 +24,7 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.core.ObservablePreferenceFragment;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.users.OwnerInfoSettings;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -31,7 +32,6 @@
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
 import com.android.settingslib.core.lifecycle.events.OnResume;
 
 public class OwnerInfoPreferenceController extends AbstractPreferenceController
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index c9d5f23..f99267e 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -24,6 +24,7 @@
 
 import android.annotation.ColorInt;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
@@ -47,6 +48,7 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.SubSettings;
 import com.android.settings.Utils;
+import com.android.settings.accessibility.AccessibilitySlicePreferenceController;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.core.SliderPreferenceController;
 import com.android.settings.core.SubSettingLauncher;
@@ -448,7 +450,17 @@
             iconResource = R.drawable.ic_settings_accent;
         }
         try {
-            return IconCompat.createWithResource(context, iconResource);
+            // LINT.IfChange(createA11yIcon)
+            if (AccessibilitySlicePreferenceController.class.getName().equals(
+                    data.getPreferenceController())) {
+                ComponentName serviceComponent = ComponentName.unflattenFromString(data.getKey());
+                return IconCompat.createWithResource(
+                        context.createPackageContext(serviceComponent.getPackageName(), 0),
+                        iconResource);
+                // LINT.ThenChange()
+            } else {
+                return IconCompat.createWithResource(context, iconResource);
+            }
         } catch (Exception e) {
             Log.w(TAG, "Falling back to settings icon because there is an error getting slice icon "
                     + data.getUri(), e);
diff --git a/src/com/android/settings/slices/SliceDataConverter.java b/src/com/android/settings/slices/SliceDataConverter.java
index f6828af3..983edc0 100644
--- a/src/com/android/settings/slices/SliceDataConverter.java
+++ b/src/com/android/settings/slices/SliceDataConverter.java
@@ -274,6 +274,12 @@
             final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             final String packageName = serviceInfo.packageName;
             final ComponentName componentName = new ComponentName(packageName, serviceInfo.name);
+
+            // If we change the flattenedName that is used to be set as a key of the Slice, we
+            // need to make corresponding change in SliceBuilderUtils, since we rely on the
+            // the A11y Service Slice's key to be a ComponentName to get the correct package name
+            // to grab the icon belongs to that package.
+            // LINT.IfChange
             final String flattenedName = componentName.flattenToString();
 
             if (!a11yServiceNames.contains(flattenedName)) {
@@ -287,6 +293,7 @@
             }
 
             sliceDataBuilder.setKey(flattenedName)
+                    // LINT.ThenChange(SliceBuilderUtils.java:createA11yIcon)
                     .setTitle(title)
                     .setUri(new Uri.Builder()
                             .scheme(ContentResolver.SCHEME_CONTENT)
diff --git a/src/com/android/settings/sound/TopLevelSoundPreferenceController.java b/src/com/android/settings/sound/TopLevelSoundPreferenceController.java
new file mode 100644
index 0000000..ddc3399
--- /dev/null
+++ b/src/com/android/settings/sound/TopLevelSoundPreferenceController.java
@@ -0,0 +1,45 @@
+/*
+ * 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.sound;
+
+import android.app.Flags;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+public class TopLevelSoundPreferenceController extends BasePreferenceController {
+
+    public TopLevelSoundPreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        preference.setSummary(Flags.modesApi() && Flags.modesUi()
+                ? R.string.sound_dashboard_summary
+                : R.string.sound_dashboard_summary_with_dnd);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
index f76bba4..d736fe5 100644
--- a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
+++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
@@ -48,9 +48,9 @@
 import com.android.settings.R
 import com.android.settings.flags.Flags
 import com.android.settings.network.SubscriptionInfoListViewModel
-import com.android.settings.network.SubscriptionUtil
 import com.android.settings.network.telephony.DataSubscriptionRepository
 import com.android.settings.network.telephony.MobileDataRepository
+import com.android.settings.network.telephony.SimRepository
 import com.android.settings.network.telephony.requireSubscriptionManager
 import com.android.settings.spa.network.PrimarySimRepository.PrimarySimInfo
 import com.android.settings.spa.search.SearchablePage
@@ -66,7 +66,6 @@
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Category
 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
-import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -213,10 +212,7 @@
         const val fileName = "NetworkCellularGroupProvider"
 
         private fun isPageSearchable(context: Context) =
-            Flags.isDualSimOnboardingEnabled() &&
-            SubscriptionUtil.isSimHardwareVisible(context) &&
-                !com.android.settingslib.Utils.isWifiOnly(context) &&
-                context.userManager.isAdminUser
+            Flags.isDualSimOnboardingEnabled() && SimRepository(context).showMobileNetworkPage()
     }
 }
 
diff --git a/src/com/android/settings/support/actionbar/HelpMenuController.java b/src/com/android/settings/support/actionbar/HelpMenuController.java
deleted file mode 100644
index 7e1f460..0000000
--- a/src/com/android/settings/support/actionbar/HelpMenuController.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.support.actionbar;
-
-import static com.android.settings.support.actionbar.HelpResourceProvider.HELP_URI_RESOURCE_KEY;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-
-import com.android.settingslib.HelpUtils;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.ObservableFragment;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
-import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
-
-/**
- * A controller that adds help menu to any Settings page.
- */
-public class HelpMenuController implements LifecycleObserver, OnCreateOptionsMenu {
-
-    private final Fragment mHost;
-
-    public static void init(@NonNull ObservablePreferenceFragment host) {
-        host.getSettingsLifecycle().addObserver(new HelpMenuController(host));
-    }
-
-    public static void init(@NonNull ObservableFragment host) {
-        host.getSettingsLifecycle().addObserver(new HelpMenuController(host));
-    }
-
-    private HelpMenuController(@NonNull Fragment host) {
-        mHost = host;
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        final Bundle arguments = mHost.getArguments();
-        int helpResourceId = 0;
-        if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) {
-            helpResourceId = arguments.getInt(HELP_URI_RESOURCE_KEY);
-        } else if (mHost instanceof HelpResourceProvider) {
-            helpResourceId = ((HelpResourceProvider) mHost).getHelpResource();
-        }
-
-        String helpUri = null;
-        if (helpResourceId != 0) {
-            helpUri = mHost.getContext().getString(helpResourceId);
-        }
-        final Activity activity = mHost.getActivity();
-        if (helpUri != null && activity != null) {
-            HelpUtils.prepareHelpMenuItem(activity, menu, helpUri, mHost.getClass().getName());
-        }
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
index 3982dc0..36578a9 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
@@ -42,7 +42,6 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
 
 import androidx.fragment.app.Fragment;
 import androidx.test.core.app.ApplicationProvider;
@@ -50,6 +49,7 @@
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.XmlTestUtils;
 import com.android.settings.testutils.shadow.ShadowAccessibilityManager;
 import com.android.settings.testutils.shadow.ShadowApplicationPackageManager;
@@ -78,6 +78,7 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLooper;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
@@ -155,6 +156,53 @@
         assertThat(indexableRawList).isNull();
     }
 
+    @DisableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+    @Test
+    public void getDynamicRawDataToIndex_hasInstalledA11yFeatures_flagOff_returnEmpty() {
+        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
+                List.of(mServiceInfo));
+        mShadowAccessibilityManager.setInstalledAccessibilityShortcutListAsUser(
+                List.of(getMockAccessibilityShortcutInfo()));
+
+        assertThat(AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+                mContext, /* enabled= */ true))
+                .isEmpty();
+    }
+
+    @EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+    @Test
+    public void getDynamicRawDataToIndex_hasInstalledA11yFeatures_flagOn_returnRawDataForInstalledA11yFeatures() {
+        mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
+                List.of(mServiceInfo));
+        mShadowAccessibilityManager.setInstalledAccessibilityShortcutListAsUser(
+                List.of(getMockAccessibilityShortcutInfo()));
+        final AccessibilitySearchFeatureProvider featureProvider =
+                FakeFeatureFactory.setupForTest().getAccessibilitySearchFeatureProvider();
+        final String synonyms = "fake keyword1, fake keyword2";
+        when(featureProvider.getSynonymsForComponent(mContext, ACTIVITY_COMPONENT_NAME))
+                .thenReturn("");
+        when(featureProvider.getSynonymsForComponent(mContext, SERVICE_COMPONENT_NAME))
+                .thenReturn(synonyms);
+
+        final List<SearchIndexableRaw> indexableRawDataList =
+                AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+                        mContext, /* enabled= */ true);
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+        assertThat(indexableRawDataList).hasSize(2);
+        SearchIndexableRaw a11yActivityIndexableData = indexableRawDataList.get(0);
+        assertThat(a11yActivityIndexableData.key).isEqualTo(
+                ACTIVITY_COMPONENT_NAME.flattenToString());
+        assertThat(a11yActivityIndexableData.title).isEqualTo(DEFAULT_LABEL);
+        assertThat(a11yActivityIndexableData.keywords).isEmpty();
+
+        SearchIndexableRaw a11yServiceIndexableData = indexableRawDataList.get(1);
+        assertThat(a11yServiceIndexableData.key).isEqualTo(
+                SERVICE_COMPONENT_NAME.flattenToString());
+        assertThat(a11yServiceIndexableData.title).isEqualTo(DEFAULT_LABEL);
+        assertThat(a11yServiceIndexableData.keywords).isEqualTo(synonyms);
+    }
+
     @Test
     public void getServiceSummary_serviceCrash_showsStopped() {
         mServiceInfo.crashed = true;
@@ -328,7 +376,7 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void onCreate_flagDisabled_haveRegisterToSpecificUrisAndActions() {
         setupFragment();
 
@@ -341,7 +389,7 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void onCreate_flagEnabled_haveRegisterToSpecificUrisAndActions() {
         setupFragment();
 
@@ -415,7 +463,7 @@
     }
 
     @Test
-    @EnableFlags(com.android.settings.accessibility.Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED)
+    @EnableFlags(Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED)
     public void testNonPreinstalledApp_IncludedInDownloadedCategory() {
         mShadowAccessibilityManager.setInstalledAccessibilityServiceList(
                 List.of(getMockAccessibilityServiceInfo(
diff --git a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
index d74794f..bbe511d 100644
--- a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
@@ -34,6 +34,7 @@
 import android.widget.SeekBar;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settings.testutils.shadow.ShadowSystemSettings;
 
 import org.junit.Before;
@@ -162,7 +163,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_left_first, 50, 50));
+                mContext.getString(R.string.audio_seek_bar_state_left_first,
+                        Utils.formatPercentage(50), Utils.formatPercentage(50)));
     }
 
     @Test
@@ -177,7 +179,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_right_first, 50, 50));
+                mContext.getString(R.string.audio_seek_bar_state_right_first,
+                        Utils.formatPercentage(50), Utils.formatPercentage(50)));
     }
 
     @Test
@@ -189,7 +192,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_left_first, 75, 25));
+                mContext.getString(R.string.audio_seek_bar_state_left_first,
+                        Utils.formatPercentage(75), Utils.formatPercentage(25)));
     }
 
     @Test
@@ -201,7 +205,8 @@
         mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true);
 
         assertThat(mSeekBar.getStateDescription()).isEqualTo(
-                mContext.getString(R.string.audio_seek_bar_state_right_first, 75, 25));
+                mContext.getString(R.string.audio_seek_bar_state_right_first,
+                        Utils.formatPercentage(75), Utils.formatPercentage(25)));
     }
 
     // method to get the center from BalanceSeekBar for testing setMax().
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt
new file mode 100644
index 0000000..07cdffb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.biometrics.fingerprint
+
+import android.app.Activity
+import android.content.Intent
+import com.android.settings.overlay.FeatureFactory
+import com.android.settings.testutils.FakeFeatureFactory
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows
+
+@RunWith(RobolectricTestRunner::class)
+class FingerprintEnrollTest {
+
+    private lateinit var featureFactory: FeatureFactory
+
+    private companion object {
+        const val INTENT_KEY = "testKey"
+        const val INTENT_VALUE = "testValue"
+        val INTENT = Intent().apply {
+            putExtra(INTENT_KEY, INTENT_VALUE)
+        }
+    }
+
+    private val activityProvider = FingerprintEnrollActivityClassProvider()
+
+    @Before
+    fun setUp() {
+        featureFactory = FakeFeatureFactory.setupForTest()
+        `when`(featureFactory.fingerprintFeatureProvider.enrollActivityClassProvider)
+            .thenReturn(activityProvider)
+    }
+
+    private fun setupActivity(activityClass: Class<out FingerprintEnroll>): FingerprintEnroll {
+        return Robolectric.buildActivity(activityClass, INTENT).create().get()
+    }
+
+    @Test
+    fun testFinishAndLaunchDefaultActivity() {
+        // Run
+        val activity = setupActivity(FingerprintEnroll::class.java)
+
+        // Verify
+        verifyLaunchNextActivity(activity, activityProvider.default)
+    }
+
+    @Test
+    fun testFinishAndLaunchSetupActivity() {
+        // Run
+        val activity = setupActivity(FingerprintEnroll.SetupActivity::class.java)
+
+        // Verify
+        verifyLaunchNextActivity(activity, activityProvider.setup)
+    }
+
+    @Test
+    fun testFinishAndLaunchInternalActivity() {
+        // Run
+        val activity = setupActivity(FingerprintEnroll.InternalActivity::class.java)
+
+        // Verify
+        verifyLaunchNextActivity(activity, activityProvider.internal)
+    }
+
+    private fun verifyLaunchNextActivity(
+        currentActivityInstance : FingerprintEnroll,
+        nextActivityClass: Class<out Activity>
+    ) {
+        assertThat(currentActivityInstance.isFinishing).isTrue()
+        val nextActivityIntent = Shadows.shadowOf(currentActivityInstance).nextStartedActivity
+        assertThat(nextActivityIntent.component!!.className).isEqualTo(nextActivityClass.name)
+        assertThat(nextActivityIntent.extras!!.size()).isEqualTo(1)
+        assertThat(nextActivityIntent.getStringExtra(INTENT_KEY)).isEqualTo(INTENT_VALUE)
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
index 8070b2e..51c0c30 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
@@ -124,10 +124,11 @@
                         listOf(
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                                "bluetooth_device_header"
+                                highlighted = false,
+                                preferenceKey = "bluetooth_device_header"
                             ),
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                                DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons"),
+                                DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, highlighted = false, preferenceKey = "action_buttons"),
                         ),
                         listOf(),
                         null))
@@ -157,7 +158,7 @@
             `when`(repository.getDeviceSettingsConfig(cachedDevice))
                 .thenReturn(
                     DeviceSettingConfigModel(
-                        listOf(), listOf(), DeviceSettingConfigItemModel.AppProvidedItem(12345)))
+                        listOf(), listOf(), DeviceSettingConfigItemModel.AppProvidedItem(12345, false)))
             val intent = Intent().apply {
                 setAction(Intent.ACTION_VIEW)
                 setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -206,10 +207,10 @@
                         listOf(
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                                "bluetooth_device_header"),
+                                highlighted = false, preferenceKey = "bluetooth_device_header"),
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
-                                "keyboard_settings"),
+                                highlighted = false, preferenceKey = "keyboard_settings"),
                         ),
                         listOf(),
                         null))
@@ -230,12 +231,14 @@
                         listOf(
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                                "bluetooth_device_header"),
+                                highlighted = false,
+                                preferenceKey = "bluetooth_device_header"),
                             DeviceSettingConfigItemModel.AppProvidedItem(
-                                DeviceSettingId.DEVICE_SETTING_ID_ANC),
+                                DeviceSettingId.DEVICE_SETTING_ID_ANC, highlighted = false),
                             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
-                                "keyboard_settings"),
+                                highlighted = false,
+                                preferenceKey = "keyboard_settings"),
                         ),
                         listOf(),
                         null))
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
index 6869c23..c3f938c 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
@@ -246,11 +246,11 @@
     }
 
     private fun getLatestLayout(layout: DeviceSettingLayout): List<List<Int>> {
-        var latestLayout = MutableList(layout.rows.size) { emptyList<Int>() }
+        val latestLayout = MutableList(layout.rows.size) { emptyList<Int>() }
         for (i in layout.rows.indices) {
             layout.rows[i]
-                .settingIds
-                .onEach { latestLayout[i] = it }
+                .columns
+                .onEach { latestLayout[i] = it.map { c -> c.settingId } }
                 .launchIn(testScope.backgroundScope)
         }
 
@@ -278,15 +278,15 @@
         DeviceSettingModel.ActionSwitchPreference(cachedDevice, settingId, "title")
 
     private fun buildRemoteSettingItem(settingId: Int) =
-        DeviceSettingConfigItemModel.AppProvidedItem(settingId)
+        DeviceSettingConfigItemModel.AppProvidedItem(settingId, false)
 
     private companion object {
         val BUILTIN_SETTING_ITEM_1 =
             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                DeviceSettingId.DEVICE_SETTING_ID_HEADER, "bluetooth_device_header")
+                DeviceSettingId.DEVICE_SETTING_ID_HEADER, false, "bluetooth_device_header")
         val BUILDIN_SETTING_ITEM_2 =
             DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
-                DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons")
-        val SETTING_ITEM_HELP = DeviceSettingConfigItemModel.AppProvidedItem(12345)
+                DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, false, "action_buttons")
+        val SETTING_ITEM_HELP = DeviceSettingConfigItemModel.AppProvidedItem(12345, false)
     }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java
index b5da88c..ff15f52 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java
@@ -150,7 +150,7 @@
     }
 
     @Test
-    public void showDialog_newMessage_dismissAndShowNewDialog() {
+    public void showDialog_newMessage_keepAndUpdateDialog() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1);
         shadowMainLooper().idle();
@@ -163,12 +163,7 @@
 
         AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2);
         shadowMainLooper().idle();
-        assertThat(dialog.isShowing()).isFalse();
-        AlertDialog newDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
-        assertThat(newDialog).isNotNull();
-        assertThat(newDialog.isShowing()).isTrue();
-        view = newDialog.findViewById(R.id.message);
-        assertThat(view).isNotNull();
+        assertThat(dialog.isShowing()).isTrue();
         assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index 354c5c7..0d21f18 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -57,7 +57,9 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.CompoundButton;
+import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
@@ -235,6 +237,7 @@
 
     @After
     public void tearDown() {
+        ShadowAlertDialogCompat.reset();
         ShadowBluetoothUtils.reset();
         ShadowThreadUtils.reset();
     }
@@ -426,6 +429,8 @@
         assertThat(childFragments)
                 .comparingElementsUsing(CLAZZNAME_EQUALS)
                 .containsExactly(AudioSharingConfirmDialogFragment.class.getName());
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -490,14 +495,21 @@
                             public void onAudioSharingProfilesConnected() {}
                         });
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        // No loading state dialog.
+        assertThat(childFragments).isEmpty();
+
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
         verify(mFeatureFactory.metricsFeatureProvider)
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
 
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        // No audio sharing dialog.
         assertThat(childFragments).isEmpty();
     }
 
@@ -514,7 +526,13 @@
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -522,8 +540,12 @@
         verify(mFeatureFactory.metricsFeatureProvider, never())
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
 
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
-        assertThat(childFragments).isEmpty();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        // No audio sharing dialog.
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).doesNotContain(
+                AudioSharingDialogFragment.class.getName());
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -534,23 +556,42 @@
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
         when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
         doNothing().when(mBroadcast).startPrivateBroadcast();
-        mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
+        mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Starting audio stream...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
         verify(mFeatureFactory.metricsFeatureProvider)
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
+        // TODO: use string res once finalized
+        expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
 
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
         assertThat(childFragments)
                 .comparingElementsUsing(CLAZZNAME_EQUALS)
-                .containsExactly(AudioSharingDialogFragment.class.getName());
+                .containsExactly(AudioSharingDialogFragment.class.getName(),
+                        AudioSharingLoadingStateDialogFragment.class.getName());
 
-        AudioSharingDialogFragment fragment =
-                (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments);
-        Pair<Integer, Object>[] eventData = fragment.getEventData();
+        Pair<Integer, Object>[] eventData = new Pair[0];
+        for (Fragment fragment : childFragments) {
+            if (fragment instanceof AudioSharingDialogFragment) {
+                eventData = ((AudioSharingDialogFragment) fragment).getEventData();
+                break;
+            }
+        }
         assertThat(eventData)
                 .asList()
                 .containsExactly(
@@ -570,6 +611,8 @@
                                 AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT
                                         .ordinal(),
                                 1));
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -582,6 +625,8 @@
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
@@ -597,6 +642,17 @@
 
         verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
         assertThat(dialog.isShowing()).isFalse();
+        // Loading state dialog shows sharing state for the user chosen sink.
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
@@ -609,6 +665,8 @@
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         verify(mBroadcast).startPrivateBroadcast();
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
@@ -624,10 +682,21 @@
 
         verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
         assertThat(dialog.isShowing()).isFalse();
+        // Loading state dialog shows sharing state for the auto add active sink.
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
     @Test
-    public void testBluetoothLeBroadcastCallbacks_updateSwitch() {
+    public void testBroadcastCallbacks_updateSwitch() {
         mOnAudioSharingStateChanged = false;
         mSwitchBar.setChecked(false);
         when(mBroadcast.isEnabled(any())).thenReturn(false);
@@ -673,7 +742,7 @@
     }
 
     @Test
-    public void testBluetoothLeBroadcastCallbacks_doNothing() {
+    public void testBroadcastCallbacks_doNothing() {
         mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
         mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1);
         mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1);
@@ -685,7 +754,7 @@
     }
 
     @Test
-    public void testBluetoothLeBroadcastAssistantCallbacks_logAction() {
+    public void testAssistantCallbacks_onSourceAddFailed_logAction() {
         mController.mBroadcastAssistantCallback.onSourceAddFailed(
                 mDevice1, mMetadata, /* reason= */ 1);
         verify(mFeatureFactory.metricsFeatureProvider)
@@ -696,7 +765,24 @@
     }
 
     @Test
-    public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() {
+    public void testAssistantCallbacks_onReceiveStateChanged_dismissLoadingDialog() {
+        AudioSharingLoadingStateDialogFragment.show(mParentFragment, TEST_DEVICE_NAME1);
+        shadowOf(Looper.getMainLooper()).idle();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+
+        BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+        when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
+        mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1,
+                state);
+        shadowOf(Looper.getMainLooper()).idle();
+        childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).isEmpty();
+    }
+
+    @Test
+    public void testAssistantCallbacks_doNothing() {
         BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
 
         // Do nothing
@@ -784,7 +870,7 @@
     @Test
     public void handleStartAudioSharingFromIntent_flagOff_doNothing() {
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-        setUpStartSharingIntent();
+        var unused = setUpFragmentWithStartSharingIntent();
         mController.onStart(mLifecycleOwner);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -795,7 +881,7 @@
     public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         when(mAssistant.isProfileReady()).thenReturn(false);
-        setUpStartSharingIntent();
+        var unused = setUpFragmentWithStartSharingIntent();
         mController.onServiceConnected();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -817,13 +903,16 @@
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
         when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
-        setUpStartSharingIntent();
+        Fragment parentFragment = setUpFragmentWithStartSharingIntent();
         mController.onServiceConnected();
         shadowOf(Looper.getMainLooper()).idle();
 
         verify(mSwitchBar).setChecked(true);
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mBroadcast).startPrivateBroadcast();
         mController.mBroadcastCallback.onPlaybackStarted(0, 0);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -831,11 +920,21 @@
                 .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING));
         verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
         verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false);
-        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
-        assertThat(childFragments).isEmpty();
+        List<Fragment> childFragments = parentFragment.getChildFragmentManager().getFragments();
+        // Skip audio sharing dialog.
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+        // The loading state dialog shows sharing state for the auto add second sink.
+        AudioSharingLoadingStateDialogFragment loadingFragment =
+                (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments);
+        // TODO: use string res once finalized
+        String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "...";
+        checkLoadingStateDialogMessage(loadingFragment, expectedMessage);
+
+        childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
-    private void setUpStartSharingIntent() {
+    private Fragment setUpFragmentWithStartSharingIntent() {
         Bundle args = new Bundle();
         args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
         Intent intent = new Intent();
@@ -849,5 +948,15 @@
                 .get();
         shadowOf(Looper.getMainLooper()).idle();
         mController.init(fragment);
+        return fragment;
+    }
+
+    private void checkLoadingStateDialogMessage(
+            @NonNull AudioSharingLoadingStateDialogFragment fragment,
+            @NonNull String expectedMessage) {
+        TextView loadingMessage = fragment.getDialog() == null ? null
+                : fragment.getDialog().findViewById(R.id.message);
+        assertThat(loadingMessage).isNotNull();
+        assertThat(loadingMessage.getText().toString()).isEqualTo(expectedMessage);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java
index c6fb361..1d39bc9 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -34,6 +36,7 @@
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.View;
 
 import androidx.lifecycle.LifecycleOwner;
@@ -72,8 +75,8 @@
             ShadowAudioStreamsHelper.class,
         })
 public class AudioStreamButtonControllerTest {
-
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final String KEY = "audio_stream_button";
     private static final int BROADCAST_ID = 1;
     private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -83,6 +86,7 @@
     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
     @Mock private AudioStreamsRepository mRepository;
     @Mock private ActionButtonsPreference mPreference;
+    @Mock private BluetoothDevice mSourceDevice;
     private Lifecycle mLifecycle;
     private LifecycleOwner mLifecycleOwner;
     private FakeFeatureFactory mFeatureFactory;
@@ -90,6 +94,7 @@
 
     @Before
     public void setUp() {
+        mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
         when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mAssistant);
         mFeatureFactory = FakeFeatureFactory.setupForTest();
@@ -255,6 +260,33 @@
     }
 
     @Test
+    public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        String address = "11:22:33:44:55:66";
+
+        BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
+        when(state.getBroadcastId()).thenReturn(BROADCAST_ID);
+        when(state.getSourceDevice()).thenReturn(mSourceDevice);
+        when(mSourceDevice.getAddress()).thenReturn(address);
+        List<Long> bisSyncState = new ArrayList<>();
+        when(state.getBisSyncState()).thenReturn(bisSyncState);
+        when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(List.of(state));
+
+        mController.displayPreference(mScreen);
+        mController.mBroadcastAssistantCallback.onReceiveStateChanged(
+                mock(BluetoothDevice.class), /* sourceId= */ 0, state);
+
+        verify(mFeatureFactory.metricsFeatureProvider, never())
+                .action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt());
+
+        // Called twice, once in displayPreference, the other one in callback
+        verify(mPreference, times(2)).setButton1Enabled(true);
+        verify(mPreference, times(2)).setButton1Text(R.string.audio_streams_disconnect);
+        verify(mPreference, times(2))
+                .setButton1Icon(com.android.settings.R.drawable.ic_settings_close);
+    }
+
+    @Test
     public void testCallback_onSourceAddFailed_updateButton() {
         when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
 
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
index 327090d..5cdc797 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -31,6 +33,7 @@
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.PreferenceScreen;
@@ -68,8 +71,9 @@
             ShadowAudioStreamsHelper.class,
         })
 public class AudioStreamHeaderControllerTest {
-
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String KEY = "audio_stream_header";
     private static final int BROADCAST_ID = 1;
     private static final String BROADCAST_NAME = "broadcast name";
@@ -81,12 +85,15 @@
     @Mock private AudioStreamDetailsFragment mFragment;
     @Mock private LayoutPreference mPreference;
     @Mock private EntityHeaderController mHeaderController;
+    @Mock private BluetoothDevice mBluetoothDevice;
     private Lifecycle mLifecycle;
     private LifecycleOwner mLifecycleOwner;
     private AudioStreamHeaderController mController;
 
     @Before
     public void setUp() {
+        mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
         ShadowEntityHeaderController.setUseMock(mHeaderController);
         ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
         when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mAssistant);
@@ -169,6 +176,44 @@
     }
 
     @Test
+    public void testDisplayPreference_sourcePresent_setSummary() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        String address = "11:22:33:44:55:66";
+
+        when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+        when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getAddress()).thenReturn(address);
+        List<Long> bisSyncState = new ArrayList<>();
+        when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+        when(mAudioStreamsHelper.getAllPresentSources())
+                .thenReturn(List.of(mBroadcastReceiveState));
+
+        mController.displayPreference(mScreen);
+
+        verify(mHeaderController).setLabel(BROADCAST_NAME);
+        verify(mHeaderController).setIcon(any(Drawable.class));
+        verify(mHeaderController)
+                .setSummary(mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY));
+        verify(mHeaderController).done(true);
+        verify(mScreen).addPreference(any());
+    }
+
+    @Test
+    public void testDisplayPreference_sourceNotPresent_setSummary() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+        when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(Collections.emptyList());
+
+        mController.displayPreference(mScreen);
+
+        verify(mHeaderController).setLabel(BROADCAST_NAME);
+        verify(mHeaderController).setIcon(any(Drawable.class));
+        verify(mHeaderController).setSummary(AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY);
+        verify(mHeaderController).done(true);
+        verify(mScreen).addPreference(any());
+    }
+
+    @Test
     public void testCallback_onSourceRemoved_updateButton() {
         when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList());
 
@@ -212,4 +257,25 @@
                 .setSummary(mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY));
         verify(mHeaderController, times(2)).done(true);
     }
+
+    @Test
+    public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        String address = "11:22:33:44:55:66";
+
+        when(mAudioStreamsHelper.getAllPresentSources())
+                .thenReturn(List.of(mBroadcastReceiveState));
+        when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
+        when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getAddress()).thenReturn(address);
+
+        mController.displayPreference(mScreen);
+        mController.mBroadcastAssistantCallback.onReceiveStateChanged(
+                mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState);
+
+        // Called twice, once in displayPreference, the other one in callback
+        verify(mHeaderController, times(2))
+                .setSummary(mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY));
+        verify(mHeaderController, times(2)).done(true);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
index e44dee9..bb873d4 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -30,6 +32,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.SpannableString;
 
 import androidx.preference.Preference;
@@ -48,6 +51,8 @@
 @RunWith(RobolectricTestRunner.class)
 public class AudioStreamStateHandlerTest {
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int SUMMARY_RES = 1;
     private static final String SUMMARY = "summary";
     private final Context mContext = spy(ApplicationProvider.getApplicationContext());
@@ -58,6 +63,7 @@
 
     @Before
     public void setUp() {
+        mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         mHandler = spy(new AudioStreamStateHandler());
     }
 
@@ -102,6 +108,28 @@
     }
 
     @Test
+    public void testHandleStateChange_setNewState_sourcePresent() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+        when(mHandler.getStateEnum())
+                .thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
+        when(mPreference.getAudioStreamState())
+                .thenReturn(
+                        AudioStreamsProgressCategoryController.AudioStreamState
+                                .ADD_SOURCE_BAD_CODE);
+
+        mHandler.handleStateChange(mPreference, mController, mHelper);
+
+        verify(mPreference)
+                .setAudioStreamState(
+                        AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
+        verify(mHandler).performAction(any(), any(), any());
+        verify(mPreference).setIsConnected(eq(true));
+        verify(mPreference).setSummary(eq(""));
+        verify(mPreference).setOnPreferenceClickListener(eq(null));
+    }
+
+    @Test
     public void testHandleStateChange_setNewState_newSummary_newListener() {
         Preference.OnPreferenceClickListener listener =
                 mock(Preference.OnPreferenceClickListener.class);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java
index 4266798..fca1137 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java
@@ -19,6 +19,8 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -37,6 +39,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.fragment.app.FragmentActivity;
 import androidx.test.core.app.ApplicationProvider;
@@ -74,6 +77,8 @@
         })
 public class AudioStreamsHelperTest {
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int GROUP_ID = 1;
     private static final int BROADCAST_ID_1 = 1;
     private static final int BROADCAST_ID_2 = 2;
@@ -86,10 +91,12 @@
     @Mock private BluetoothLeBroadcastMetadata mMetadata;
     @Mock private CachedBluetoothDevice mCachedDevice;
     @Mock private BluetoothDevice mDevice;
+    @Mock private BluetoothDevice mSourceDevice;
     private AudioStreamsHelper mHelper;
 
     @Before
     public void setUp() {
+        mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile())
@@ -166,6 +173,7 @@
 
     @Test
     public void removeSource_memberHasConnectedSource() {
+        String address = "11:22:33:44:55:66";
         List<BluetoothDevice> devices = new ArrayList<>();
         var memberDevice = mock(BluetoothDevice.class);
         devices.add(mDevice);
@@ -184,6 +192,8 @@
         List<Long> bisSyncState = new ArrayList<>();
         bisSyncState.add(1L);
         when(source.getBisSyncState()).thenReturn(bisSyncState);
+        when(source.getSourceDevice()).thenReturn(mSourceDevice);
+        when(mSourceDevice.getAddress()).thenReturn(address);
 
         mHelper.removeSource(BROADCAST_ID_2);
 
@@ -218,6 +228,52 @@
     }
 
     @Test
+    public void getAllPresentSources_noSource() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+        List<BluetoothDevice> devices = new ArrayList<>();
+        devices.add(mDevice);
+
+        String address = "00:00:00:00:00:00";
+
+        when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
+        BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
+        when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+        when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
+        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(source));
+        when(source.getSourceDevice()).thenReturn(mSourceDevice);
+        when(mSourceDevice.getAddress()).thenReturn(address);
+
+        var list = mHelper.getAllPresentSources();
+        assertThat(list).isEmpty();
+    }
+
+    @Test
+    public void getAllPresentSources_returnSource() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        String address = "11:22:33:44:55:66";
+
+        List<BluetoothDevice> devices = new ArrayList<>();
+        devices.add(mDevice);
+
+        when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
+        BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
+        when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
+        when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
+        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(source));
+        when(source.getSourceDevice()).thenReturn(mSourceDevice);
+        when(mSourceDevice.getAddress()).thenReturn(address);
+        List<Long> bisSyncState = new ArrayList<>();
+        when(source.getBisSyncState()).thenReturn(bisSyncState);
+
+        var list = mHelper.getAllPresentSources();
+        assertThat(list).isNotEmpty();
+        assertThat(list.get(0)).isEqualTo(source);
+    }
+
+    @Test
     public void startMediaService_noDevice_doNothing() {
         mHelper.startMediaService(mContext, BROADCAST_ID_1, BROADCAST_NAME);
 
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
index 164c2f0..1e64528 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -25,6 +27,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -41,14 +44,18 @@
 @RunWith(RobolectricTestRunner.class)
 public class AudioStreamsProgressCategoryCallbackTest {
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock private AudioStreamsProgressCategoryController mController;
     @Mock private BluetoothDevice mDevice;
     @Mock private BluetoothLeBroadcastReceiveState mState;
     @Mock private BluetoothLeBroadcastMetadata mMetadata;
+    @Mock private BluetoothDevice mSourceDevice;
     private AudioStreamsProgressCategoryCallback mCallback;
 
     @Before
     public void setUp() {
+        mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         mCallback = new AudioStreamsProgressCategoryCallback(mController);
     }
 
@@ -63,6 +70,20 @@
     }
 
     @Test
+    public void testOnReceiveStateChanged_sourcePresent() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        String address = "11:22:33:44:55:66";
+
+        List<Long> bisSyncState = new ArrayList<>();
+        when(mState.getBisSyncState()).thenReturn(bisSyncState);
+        when(mState.getSourceDevice()).thenReturn(mSourceDevice);
+        when(mSourceDevice.getAddress()).thenReturn(address);
+        mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
+
+        verify(mController).handleSourcePresent(any());
+    }
+
+    @Test
     public void testOnReceiveStateChanged_badCode() {
         when(mState.getPaSyncState())
                 .thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
index fd1b649..227748a 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
@@ -20,10 +20,12 @@
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SYNCED;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
 import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID;
 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,12 +43,14 @@
 import static java.util.Collections.emptyList;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudioContentMetadata;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
@@ -96,6 +100,8 @@
         })
 public class AudioStreamsProgressCategoryControllerTest {
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String VALID_METADATA =
             "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
                     + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
@@ -115,6 +121,7 @@
     @Mock private BluetoothLeBroadcastMetadata mMetadata;
     @Mock private CachedBluetoothDevice mDevice;
     @Mock private AudioStreamsProgressCategoryPreference mPreference;
+    @Mock private BluetoothDevice mSourceDevice;
     private Lifecycle mLifecycle;
     private LifecycleOwner mLifecycleOwner;
     private Fragment mFragment;
@@ -125,6 +132,7 @@
         ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
         when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
         when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
+        mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
 
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
         when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
@@ -283,6 +291,29 @@
     }
 
     @Test
+    public void testOnStart_initHasDevice_getPresentSources() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+
+        // Setup a device
+        ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+        List<BluetoothLeBroadcastReceiveState> connectedList = new ArrayList<>();
+        // Empty connected device list
+        when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(connectedList);
+
+        mController.onStart(mLifecycleOwner);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mAudioStreamsHelper).getAllPresentSources();
+        verify(mLeBroadcastAssistant).startSearchingForSources(any());
+
+        var dialog = ShadowAlertDialog.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+
+        verify(mController, never()).moveToState(any(), any());
+    }
+
+    @Test
     public void testOnStart_handleSourceFromQrCode() {
         // Setup a device
         ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
@@ -764,6 +795,58 @@
         assertThat(states.get(1)).isEqualTo(ADD_SOURCE_FAILED);
     }
 
+    @Test
+    public void testHandleSourcePresent_updateState() {
+        mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        String address = "11:22:33:44:55:66";
+
+        // Setup a device
+        ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+        // Setup mPreference so it's not null
+        mController.displayPreference(mScreen);
+
+        // A new source found
+        when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+        mController.handleSourceFound(mMetadata);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        // The connected source is identified as having a bad code
+        BluetoothLeBroadcastReceiveState receiveState =
+                mock(BluetoothLeBroadcastReceiveState.class);
+        when(receiveState.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+        when(receiveState.getSourceDevice()).thenReturn(mSourceDevice);
+        when(mSourceDevice.getAddress()).thenReturn(address);
+        List<Long> bisSyncState = new ArrayList<>();
+        when(receiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+        // The new found source is identified as failed to connect
+        mController.handleSourcePresent(receiveState);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        ArgumentCaptor<AudioStreamPreference> preference =
+                ArgumentCaptor.forClass(AudioStreamPreference.class);
+        ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+                ArgumentCaptor.forClass(
+                        AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+        verify(mController, times(2)).moveToState(preference.capture(), state.capture());
+        List<AudioStreamPreference> preferences = preference.getAllValues();
+        assertThat(preferences.size()).isEqualTo(2);
+        List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
+        assertThat(states.size()).isEqualTo(2);
+
+        // Verify one preference is created with SYNCED
+        assertThat(preferences.get(0).getAudioStreamBroadcastId())
+                .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+        assertThat(states.get(0)).isEqualTo(SYNCED);
+
+        // Verify the preference is updated to state ADD_SOURCE_FAILED
+        assertThat(preferences.get(1).getAudioStreamBroadcastId())
+                .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+        assertThat(states.get(1)).isEqualTo(SOURCE_PRESENT);
+    }
+
     private static BluetoothLeBroadcastReceiveState createConnectedMock(int id) {
         var connected = mock(BluetoothLeBroadcastReceiveState.class);
         List<Long> bisSyncState = new ArrayList<>();
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentStateTest.java
new file mode 100644
index 0000000..fd84fef
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourcePresentStateTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.audiosharing.audiostreams;
+
+import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
+
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.SourcePresentState.AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowFragment;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+        shadows = {
+            ShadowFragment.class,
+        })
+public class SourcePresentStateTest {
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    private static final int BROADCAST_ID = 1;
+    private static final String BROADCAST_TITLE = "title";
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock private AudioStreamPreference mPreference;
+    @Mock private AudioStreamsProgressCategoryController mController;
+    @Mock private AudioStreamsHelper mHelper;
+    @Mock private AudioStreamsRepository mRepository;
+    @Mock private AudioStreamsDashboardFragment mFragment;
+    @Mock private FragmentActivity mActivity;
+    private FakeFeatureFactory mFeatureFactory;
+    private SourcePresentState mInstance;
+
+    @Before
+    public void setUp() {
+        when(mFragment.getActivity()).thenReturn(mActivity);
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+        mInstance = new SourcePresentState();
+        when(mPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
+        when(mPreference.getTitle()).thenReturn(BROADCAST_TITLE);
+    }
+
+    @Test
+    public void testGetInstance() {
+        mInstance = SourcePresentState.getInstance();
+        assertThat(mInstance).isNotNull();
+        assertThat(mInstance).isInstanceOf(SourcePresentState.class);
+    }
+
+    @Test
+    public void testGetSummary() {
+        int summary = mInstance.getSummary();
+        assertThat(summary).isEqualTo(AUDIO_STREAM_SOURCE_PRESENT_STATE_SUMMARY);
+    }
+
+    @Test
+    public void testGetStateEnum() {
+        AudioStreamsProgressCategoryController.AudioStreamState stateEnum =
+                mInstance.getStateEnum();
+        assertThat(stateEnum)
+                .isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_PRESENT);
+    }
+
+    @Test
+    public void testGetOnClickListener_startSubSettings() {
+        when(mController.getFragment()).thenReturn(mFragment);
+        when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
+
+        Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mController);
+        assertThat(listener).isNotNull();
+
+        // mContext is not an Activity context, calling startActivity() from outside of an Activity
+        // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
+        // AndroidRuntimeException.
+        Context activityContext = mock(Context.class);
+        when(mPreference.getContext()).thenReturn(activityContext);
+
+        listener.onPreferenceClick(mPreference);
+
+        ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(activityContext).startActivity(argumentCaptor.capture());
+
+        Intent intent = argumentCaptor.getValue();
+        assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+                .isEqualTo(AudioStreamDetailsFragment.class.getName());
+        assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+                .isEqualTo(R.string.audio_streams_detail_page_title);
+        assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
+                .isEqualTo(AUDIO_STREAM_MAIN);
+
+        Bundle bundle = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.getString(AudioStreamDetailsFragment.BROADCAST_NAME_ARG))
+                .isEqualTo(BROADCAST_TITLE);
+        assertThat(bundle.getInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG))
+                .isEqualTo(BROADCAST_ID);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
index 051eda7..c7d0c60 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
@@ -59,6 +59,11 @@
         return sMockHelper.getAllConnectedSources();
     }
 
+    @Implementation
+    public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
+        return sMockHelper.getAllPresentSources();
+    }
+
     /** Gets {@link CachedBluetoothDevice} in sharing or le connected */
     @Implementation
     public static Optional<CachedBluetoothDevice> getCachedBluetoothDeviceInSharingOrLeConnected(
diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java
new file mode 100644
index 0000000..68cd768
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class PointerTouchpadFragmentTest {
+
+    private PointerTouchpadFragment mFragment;
+
+    @Before
+    public void setUp() {
+        mFragment = new PointerTouchpadFragment();
+    }
+
+    @Test
+    public void getPreferenceScreenResId_isPointerTouchpad() {
+        assertThat(mFragment.getPreferenceScreenResId())
+                .isEqualTo(R.xml.accessibility_pointer_and_touchpad);
+    }
+
+    @Test
+    public void getMetricsCategory_returnsCorrectCategory() {
+        assertThat(mFragment.getMetricsCategory()).isEqualTo(
+                SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java
new file mode 100644
index 0000000..6fceeba
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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.inputmethod;
+
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.view.InputDevice;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.shadow.ShadowInputDevice;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link PointerTouchpadPreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowInputDevice.class,
+})
+public final class PointerTouchpadPreferenceControllerTest {
+    @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+    private PointerTouchpadPreferenceController mController;
+
+    @Before
+    public void initObjects() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mController = new PointerTouchpadPreferenceController(context, "pointer_touchpad");
+        ShadowInputDevice.reset();
+    }
+
+    @Test
+    public void getAvailableStatus_noTouchpadOrMouseConditionallyUnavailable() {
+        int deviceId = 1;
+        ShadowInputDevice.sDeviceIds = new int[]{deviceId};
+        InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
+                InputDevice.SOURCE_BLUETOOTH_STYLUS);
+        ShadowInputDevice.addDevice(deviceId, device);
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_isTouchpadAvailable() {
+        int deviceId = 1;
+        ShadowInputDevice.sDeviceIds = new int[]{deviceId};
+        InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
+                InputDevice.SOURCE_TOUCHPAD);
+        ShadowInputDevice.addDevice(deviceId, device);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.AVAILABLE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_isMouseAvailable() {
+        int deviceId = 1;
+        ShadowInputDevice.sDeviceIds = new int[]{deviceId};
+        InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId,
+                InputDevice.SOURCE_MOUSE);
+        ShadowInputDevice.addDevice(deviceId, device);
+
+        assertThat(mController.getAvailabilityStatus())
+                .isEqualTo(BasePreferenceController.AVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
index 8d6d2d9..1823d6d 100644
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
@@ -32,7 +32,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.os.UserManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -73,8 +72,6 @@
     @Mock
     private PreferenceScreen mPreferenceScreen;
     @Mock
-    private UserManager mUserManager;
-    @Mock
     private MobileNetworkRepository mMobileNetworkRepository;
     @Mock
     private MobileNetworkRepository.MobileNetworkCallback mMobileNetworkCallback;
@@ -92,7 +89,6 @@
         doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
         doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
         doReturn(mEuiccManager).when(mContext).getSystemService(EuiccManager.class);
-        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
         mMobileNetworkRepository = MobileNetworkRepository.getInstance(mContext);
         mLifecycleOwner = () -> mLifecycle;
         mLifecycle = new Lifecycle(mLifecycleOwner);
@@ -119,21 +115,6 @@
     }
 
     @Test
-    public void isAvailable_wifiOnlyMode_notAvailable() {
-        when(mTelephonyManager.isDataCapable()).thenReturn(false);
-        when(mUserManager.isAdminUser()).thenReturn(true);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
-    public void isAvailable_secondaryUser_notAvailable() {
-        when(mTelephonyManager.isDataCapable()).thenReturn(true);
-        when(mUserManager.isAdminUser()).thenReturn(false);
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
     public void getSummary_noSubscriptions_returnSummaryCorrectly() {
         mController.displayPreference(mPreferenceScreen);
         mController.onResume();
diff --git a/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java
deleted file mode 100644
index 8e0c863..0000000
--- a/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.UserManager;
-import android.text.BidiFormatter;
-import android.util.FeatureFlagUtils;
-
-import com.android.settings.R;
-import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
-import com.android.settings.testutils.shadow.ShadowUserManager;
-import com.android.settings.testutils.shadow.ShadowUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {
-        ShadowRestrictedLockUtilsInternal.class,
-        ShadowUtils.class,
-        ShadowUserManager.class,
-})
-public class TopLevelNetworkEntryPreferenceControllerTest {
-
-    @Mock
-    private MobileNetworkPreferenceController mMobileNetworkPreferenceController;;
-
-    private Context mContext;
-    private TopLevelNetworkEntryPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-        final ShadowUserManager um = Shadow.extract(
-                RuntimeEnvironment.application.getSystemService(UserManager.class));
-        um.setIsAdminUser(true);
-
-        mController = new TopLevelNetworkEntryPreferenceController(mContext, "test_key");
-
-        ReflectionHelpers.setField(mController, "mMobileNetworkPreferenceController",
-                mMobileNetworkPreferenceController);
-    }
-
-    @After
-    public void tearDown() {
-        ShadowUtils.reset();
-    }
-
-    @Test
-    public void getAvailabilityStatus_demoUser_nonLargeScreen_unsupported() {
-        ShadowUtils.setIsDemoUser(true);
-        FeatureFlagUtils.setEnabled(mContext, "settings_support_large_screen", false);
-        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
-    }
-
-    @Test
-    public void getSummary_hasMobile_shouldReturnMobileSummary() {
-        when(mMobileNetworkPreferenceController.isAvailable()).thenReturn(true);
-
-        assertThat(mController.getSummary()).isEqualTo(BidiFormatter.getInstance().unicodeWrap(
-                mContext.getString(R.string.network_dashboard_summary_mobile)));
-    }
-
-    @Test
-    public void getSummary_noMobile_shouldReturnNoMobileSummary() {
-        when(mMobileNetworkPreferenceController.isAvailable()).thenReturn(false);
-
-        assertThat(mController.getSummary()).isEqualTo(BidiFormatter.getInstance().unicodeWrap(
-                mContext.getString(R.string.network_dashboard_summary_no_mobile)));
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java
index fd79515..2b0d6e7 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBehaviorFooterPreferenceControllerTest.java
@@ -43,7 +43,6 @@
 
 import com.android.settings.R;
 import com.android.settings.notification.zen.AbstractZenModePreferenceController.ZenModeConfigWrapper;
-import com.android.settings.notification.zen.ZenModeBehaviorFooterPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -206,7 +205,7 @@
         ZenRule injectedRule = spy(new ZenRule());
         injectedRule.zenMode = ZEN_MODE_ALARMS;
         injectedRule.component = mock(ComponentName.class);
-        when(injectedRule.isAutomaticActive()).thenReturn(true);
+        when(injectedRule.isActive()).thenReturn(true);
         when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
         injectedAutomaticRules.put("testid", injectedRule);
 
@@ -226,7 +225,7 @@
         ZenRule injectedRule = spy(new ZenRule());
         injectedRule.zenMode = ZEN_MODE_NO_INTERRUPTIONS;
         injectedRule.component = mock(ComponentName.class);
-        when(injectedRule.isAutomaticActive()).thenReturn(true);
+        when(injectedRule.isActive()).thenReturn(true);
         when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
         injectedAutomaticRules.put("testid", injectedRule);
 
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java
index efa2f55..e5c2d42 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSettingsFooterPreferenceControllerTest.java
@@ -44,7 +44,6 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.notification.zen.AbstractZenModePreferenceController.ZenModeConfigWrapper;
-import com.android.settings.notification.zen.ZenModeSettingsFooterPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -289,7 +288,7 @@
         injectedRule.component = mock(ComponentName.class);
         injectedRule.name = nameAndId;
         injectedRule.conditionId = new Uri.Builder().authority(nameAndId).build(); // unique uri
-        when(injectedRule.isAutomaticActive()).thenReturn(isActive);
+        when(injectedRule.isActive()).thenReturn(isActive);
         when(mConfigWrapper.isTimeRule(injectedRule.conditionId)).thenReturn(!isApp);
         if (isApp) {
             when(injectedRule.component.getPackageName()).thenReturn(TEST_APP_NAME);
diff --git a/tests/robotests/src/com/android/settings/security/OwnerInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/OwnerInfoPreferenceControllerTest.java
index 81f4fce..0db950b 100644
--- a/tests/robotests/src/com/android/settings/security/OwnerInfoPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/OwnerInfoPreferenceControllerTest.java
@@ -36,11 +36,11 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.core.ObservablePreferenceFragment;
 import com.android.settings.users.OwnerInfoSettings;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java b/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java
deleted file mode 100644
index 8cd26da..0000000
--- a/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java
+++ /dev/null
@@ -1,78 +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.support.actionbar;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment;
-
-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;
-
-@RunWith(RobolectricTestRunner.class)
-public class HelpMenuControllerTest {
-
-    @Mock
-    private Context mContext;
-    private TestFragment mHost;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mHost = spy(new TestFragment());
-        doReturn(mContext).when(mHost).getContext();
-    }
-
-    @Test
-    public void onCreateOptionsMenu_withArgumentOverride_shouldPrepareHelpUsingOverride() {
-        final Bundle bundle = new Bundle();
-        bundle.putInt(HelpResourceProvider.HELP_URI_RESOURCE_KEY, 123);
-        mHost.setArguments(bundle);
-
-        HelpMenuController.init(mHost);
-
-        mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */);
-
-        verify(mContext).getString(123);
-    }
-
-    @Test
-    public void onCreateOptionsMenu_noArgumentOverride_shouldPrepareHelpUsingProvider() {
-        HelpMenuController.init(mHost);
-
-        mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */);
-
-        verify(mContext).getString(mHost.getHelpResource());
-    }
-
-    private static class TestFragment extends ObservablePreferenceFragment
-        implements HelpResourceProvider {
-
-        @Override
-        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        }
-    }
-}
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
index 3ba4bac..4bb5f2f 100644
--- a/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkListFragmentTest.kt
@@ -17,57 +17,37 @@
 package com.android.settings.network
 
 import android.content.Context
-import android.content.res.Resources
-import android.os.UserManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.R
-import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settings.network.MobileNetworkListFragment.Companion.SearchIndexProvider
+import com.android.settings.network.telephony.SimRepository
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
 import org.mockito.kotlin.stub
 
 @RunWith(AndroidJUnit4::class)
 class MobileNetworkListFragmentTest {
-    private val mockUserManager = mock<UserManager>()
+    private val mockSimRepository = mock<SimRepository>()
 
-    private val mockResources = mock<Resources>()
-
-    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
-        on { userManager } doReturn mockUserManager
-        on { resources } doReturn mockResources
-    }
+    private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Test
-    fun isPageSearchEnabled_adminUser_shouldReturnTrue() {
-        mockUserManager.stub {
-            on { isAdminUser } doReturn true
-        }
-        mockResources.stub {
-            on { getBoolean(R.bool.config_show_sim_info) } doReturn true
-        }
+    fun isPageSearchEnabled_showMobileNetworkPage_returnTrue() {
+        mockSimRepository.stub { on { showMobileNetworkPage() } doReturn true }
 
-        val isEnabled =
-            MobileNetworkListFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(context)
+        val isEnabled = SearchIndexProvider { mockSimRepository }.isPageSearchEnabled(context)
 
         assertThat(isEnabled).isTrue()
     }
 
     @Test
-    fun isPageSearchEnabled_nonAdminUser_shouldReturnFalse() {
-        mockUserManager.stub {
-            on { isAdminUser } doReturn false
-        }
-        mockResources.stub {
-            on { getBoolean(R.bool.config_show_sim_info) } doReturn true
-        }
+    fun isPageSearchEnabled_hideMobileNetworkPage_returnFalse() {
+        mockSimRepository.stub { on { showMobileNetworkPage() } doReturn false }
 
-        val isEnabled =
-            MobileNetworkListFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(context)
+        val isEnabled = SearchIndexProvider { mockSimRepository }.isPageSearchEnabled(context)
 
         assertThat(isEnabled).isFalse()
     }
diff --git a/tests/spa_unit/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.kt
new file mode 100644
index 0000000..27c9602
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.text.BidiFormatter
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.network.telephony.SimRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class TopLevelNetworkEntryPreferenceControllerTest {
+
+    private val mockSimRepository = mock<SimRepository>()
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private var isDemoUser = false
+    private var isEmbeddingActivityEnabled = false
+
+    private var controller =
+        TopLevelNetworkEntryPreferenceController(
+            context = context,
+            preferenceKey = TEST_KEY,
+            simRepository = mockSimRepository,
+            isDemoUser = { isDemoUser },
+            isEmbeddingActivityEnabled = { isEmbeddingActivityEnabled },
+        )
+
+    @Test
+    fun getAvailabilityStatus_demoUser_largeScreen_unsupported() {
+        isDemoUser = true
+        isEmbeddingActivityEnabled = true
+
+        val availabilityStatus = controller.availabilityStatus
+
+        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_demoUser_nonLargeScreen_unsupported() {
+        isDemoUser = true
+        isEmbeddingActivityEnabled = false
+
+        val availabilityStatus = controller.availabilityStatus
+
+        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE)
+    }
+
+    @Test
+    fun getSummary_hasMobile_shouldReturnMobileSummary() {
+        mockSimRepository.stub { on { showMobileNetworkPage() } doReturn true }
+
+        val summary = controller.summary
+
+        assertThat(summary)
+            .isEqualTo(
+                BidiFormatter.getInstance()
+                    .unicodeWrap(context.getString(R.string.network_dashboard_summary_mobile))
+            )
+    }
+
+    @Test
+    fun getSummary_noMobile_shouldReturnNoMobileSummary() {
+        mockSimRepository.stub { on { showMobileNetworkPage() } doReturn false }
+
+        val summary = controller.summary
+
+        assertThat(summary)
+            .isEqualTo(
+                BidiFormatter.getInstance()
+                    .unicodeWrap(context.getString(R.string.network_dashboard_summary_no_mobile))
+            )
+    }
+
+    private companion object {
+        const val TEST_KEY = "test_key"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SimRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SimRepositoryTest.kt
new file mode 100644
index 0000000..bbcac08
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SimRepositoryTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.network.telephony
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class SimRepositoryTest {
+
+    private val mockUserManager = mock<UserManager>()
+
+    private val mockPackageManager = mock<PackageManager>()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { userManager } doReturn mockUserManager
+        on { packageManager } doReturn mockPackageManager
+    }
+
+    private val repository = SimRepository(context)
+
+    @Test
+    fun showMobileNetworkPage_adminUserAndHasTelephony_returnTrue() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn true
+        }
+        mockPackageManager.stub {
+            on { hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } doReturn true
+        }
+
+        val showMobileNetworkPage = repository.showMobileNetworkPage()
+
+        assertThat(showMobileNetworkPage).isTrue()
+    }
+
+    @Test
+    fun showMobileNetworkPage_notAdminUser_returnFalse() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn false
+        }
+        mockPackageManager.stub {
+            on { hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } doReturn true
+        }
+
+        val showMobileNetworkPage = repository.showMobileNetworkPage()
+
+        assertThat(showMobileNetworkPage).isFalse()
+    }
+
+    @Test fun showMobileNetworkPage_noTelephony_returnFalse() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn true
+        }
+        mockPackageManager.stub {
+            on { hasSystemFeature(PackageManager.FEATURE_TELEPHONY) } doReturn false
+        }
+
+        val showMobileNetworkPage = repository.showMobileNetworkPage()
+
+        assertThat(showMobileNetworkPage).isFalse()
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
index 5dbc534..5052f57 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt
@@ -120,7 +120,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.simSlotIndex })
             .containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder()
@@ -141,7 +141,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.simSlotIndex })
             .containsExactly(SIM_SLOT_INDEX_1, SubscriptionManager.INVALID_SIM_SLOT_INDEX).inOrder()
@@ -164,7 +164,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0)
     }
@@ -184,7 +184,7 @@
             )
         }
 
-        val subInfos = context.getSelectableSubscriptionInfoList()
+        val subInfos = repository.getSelectableSubscriptionInfoList()
 
         assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT)
     }
diff --git a/tests/unit/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java
deleted file mode 100644
index 1231c01..0000000
--- a/tests/unit/src/com/android/settings/network/MobileNetworkPreferenceControllerTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settings.network;
-
-import static androidx.lifecycle.Lifecycle.Event;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Looper;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.telephony.PhoneStateListener;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import com.android.settingslib.RestrictedPreference;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidJUnit4.class)
-public class MobileNetworkPreferenceControllerTest {
-    private Context mContext;
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private SubscriptionManager mSubscriptionManager;
-
-    @Mock
-    private UserManager mUserManager;
-
-    private PreferenceManager mPreferenceManager;
-    private PreferenceScreen mScreen;
-
-    @Mock
-    private LifecycleOwner mLifecycleOwner;
-    private LifecycleRegistry mLifecycleRegistry;
-    private MobileNetworkPreferenceController mController;
-    private Preference mPreference;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(ApplicationProvider.getApplicationContext());
-        when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
-        when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
-        when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
-        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-        mPreferenceManager = new PreferenceManager(mContext);
-        mScreen = mPreferenceManager.createPreferenceScreen(mContext);
-        mPreference = new Preference(mContext);
-        mPreference.setKey(MobileNetworkPreferenceController.KEY_MOBILE_NETWORK_SETTINGS);
-
-        mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
-        when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
-    }
-
-    @Test
-    public void secondaryUser_prefIsNotAvailable() {
-        when(mUserManager.isAdminUser()).thenReturn(false);
-        when(mTelephonyManager.isDataCapable()).thenReturn(true);
-
-        mController = new MobileNetworkPreferenceController(mContext);
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
-    public void wifiOnly_prefIsNotAvailable() {
-        when(mUserManager.isAdminUser()).thenReturn(true);
-        when(mTelephonyManager.isDataCapable()).thenReturn(false);
-
-        mController = new MobileNetworkPreferenceController(mContext);
-        assertThat(mController.isAvailable()).isFalse();
-    }
-
-    @Test
-    @UiThreadTest
-    public void goThroughLifecycle_isAvailable_shouldListenToServiceChange() {
-        mController = spy(new MobileNetworkPreferenceController(mContext));
-        mLifecycleRegistry.addObserver(mController);
-        doReturn(true).when(mController).isAvailable();
-
-        mLifecycleRegistry.handleLifecycleEvent(Event.ON_START);
-        verify(mController).onStart();
-        verify(mTelephonyManager).registerTelephonyCallback(
-                mContext.getMainExecutor(), mController.mTelephonyCallback);
-
-        mLifecycleRegistry.handleLifecycleEvent(Event.ON_STOP);
-        verify(mController).onStop();
-        verify(mTelephonyManager).unregisterTelephonyCallback(mController.mTelephonyCallback);
-    }
-
-    @Test
-    @UiThreadTest
-    public void serviceStateChange_shouldUpdatePrefSummary() {
-        final String testCarrierName = "test";
-
-        mController = spy(new MobileNetworkPreferenceController(mContext));
-        mLifecycleRegistry.addObserver(mController);
-        doReturn(true).when(mController).isAvailable();
-
-        mScreen.addPreference(mPreference);
-
-        // Display pref and go through lifecycle to set up listener.
-        mController.displayPreference(mScreen);
-        mLifecycleRegistry.handleLifecycleEvent(Event.ON_START);
-        verify(mController).onStart();
-        verify(mTelephonyManager).registerTelephonyCallback(
-                mContext.getMainExecutor(), mController.mTelephonyCallback);
-
-        doReturn(testCarrierName).when(mController).getSummary();
-
-        mController.mTelephonyCallback.onServiceStateChanged(null);
-
-        // Carrier name should be set.
-        Assert.assertEquals(mPreference.getSummary(), testCarrierName);
-    }
-
-    @Test
-    public void airplaneModeTurnedOn_shouldDisablePreference() {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Global.AIRPLANE_MODE_ON, 1);
-        mController = spy(new MobileNetworkPreferenceController(mContext));
-        final RestrictedPreference mPreference = new RestrictedPreference(mContext);
-        mController.updateState(mPreference);
-        assertThat(mPreference.isEnabled()).isFalse();
-    }
-
-    @Test
-    public void airplaneModeTurnedOffAndNoUserRestriction_shouldEnablePreference() {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Global.AIRPLANE_MODE_ON, 0);
-        mController = spy(new MobileNetworkPreferenceController(mContext));
-        final RestrictedPreference mPreference = new RestrictedPreference(mContext);
-        mPreference.setDisabledByAdmin(null);
-        mController.updateState(mPreference);
-        assertThat(mPreference.isEnabled()).isTrue();
-    }
-
-    @Test
-    public void airplaneModeTurnedOffAndHasUserRestriction_shouldDisablePreference() {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Global.AIRPLANE_MODE_ON, 0);
-        mController = spy(new MobileNetworkPreferenceController(mContext));
-        final RestrictedPreference mPreference = new RestrictedPreference(mContext);
-        mPreference.setDisabledByAdmin(EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN);
-        mController.updateState(mPreference);
-        assertThat(mPreference.isEnabled()).isFalse();
-    }
-}