Merge "Support flagging for PreferenceScreenCreator" into main
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0999f0a..887491d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7867,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>
@@ -13577,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/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/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/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/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/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/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/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/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/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);