Merge "Fix startActivity for multi-user issue HSUM mode" into main
diff --git a/aconfig/catalyst/system.aconfig b/aconfig/catalyst/system.aconfig
new file mode 100644
index 0000000..f87ff44
--- /dev/null
+++ b/aconfig/catalyst/system.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settings.flags"
+container: "system"
+
+flag {
+    name: "catalyst_language_setting"
+    namespace: "android_settings"
+    description: "Flag for System -> Languages screen"
+    bug: "323791114"
+}
diff --git a/aconfig/settings_telephony_flag_declarations.aconfig b/aconfig/settings_telephony_flag_declarations.aconfig
index dab1b45..0279125 100644
--- a/aconfig/settings_telephony_flag_declarations.aconfig
+++ b/aconfig/settings_telephony_flag_declarations.aconfig
@@ -14,3 +14,19 @@
     description: "Control the Dual SIM onobarding feature"
     bug: "298898436"
 }
+
+# OWNER=yomna TARGET=25Q2
+flag {
+    name: "mobile_network_security_2g"
+    namespace: "cellular_security"
+    description: "Exposing 2G toggles in Mobile Network Security page"
+    bug: "355062720"
+}
+
+# OWNER=yomna TARGET=25Q2
+flag {
+    name: "add_security_transparency_to_eng_menu"
+    namespace: "cellular_security"
+    description: "Exposing security transparency features to field engineering menu"
+    bug: "355062720"
+}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2ee6d60..75e901e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -260,6 +260,13 @@
     <string name="bluetooth_disable_leaudio_summary">Disables Bluetooth LE audio feature if the device supports LE audio hardware capabilities.</string>
     <!-- Setting toggle title for switch Bluetooth LE Audio mode. [CHAR LIMIT=40] -->
     <string name="bluetooth_leaudio_mode">Bluetooth LE Audio mode</string>
+
+    <!-- Setting toggle title for enabling Bluetooth LE Audio UI preview. [CHAR LIMIT=none] -->
+    <string name="bluetooth_leaudio_broadcast_ui">Enable Bluetooth LE Audio Broadcast UI preview</string>
+    <!-- Summary of toggle for enabling Bluetooth LE Audio UI preview. [CHAR LIMIT=none]-->
+    <string name="bluetooth_leaudio_broadcast_ui_summary">Enables the LE Audio Sharing UI preview
+        including personal audio sharing and private broadcast</string>
+
     <!-- Setting toggle title for enabling Bluetooth LE Audio toggle in Device Details. [CHAR LIMIT=40] -->
     <string name="bluetooth_show_leaudio_device_details">Show LE audio toggle in Device Details</string>
 
@@ -5995,6 +6002,10 @@
 
     <!-- Title for the battery summary tip [CHAR LIMIT=NONE] -->
     <string name="battery_tip_summary_title">Apps are running normally</string>
+    <!-- Title for the battery replacement tip [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_replacement_title">Battery replacement recommended</string>
+    <!-- Summary for the battery replacement tip [CHAR LIMIT=NONE] -->
+    <string name="battery_tip_replacement_summary">Battery capacity and charging performance are reduced, and battery replacement is recommended.</string>
     <!-- Title for the low battery tip [CHAR LIMIT=NONE] -->
     <string name="battery_tip_low_battery_title">Battery level low</string>
     <!-- Summary for the low battery tip [CHAR LIMIT=NONE] -->
@@ -7417,6 +7428,8 @@
     <string name="help_url_battery" translatable="false"></string>
     <!-- Help URL, Battery Defender [DO NOT TRANSLATE] -->
     <string name="help_url_battery_defender" translatable="false"></string>
+    <!-- Help URL, Battery Replacement [DO NOT TRANSLATE] -->
+    <string name="help_url_battery_replacement" translatable="false"></string>
     <!-- Help URL, Dock Defender [DO NOT TRANSLATE] -->
     <string name="help_url_dock_defender" translatable="false"></string>
     <!-- Help URL, Incompatible charging [DO NOT TRANSLATE] -->
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index a4addf6..dde397b 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -394,6 +394,11 @@
             android:entryValues="@array/bluetooth_leaudio_mode_values"/>
 
         <SwitchPreferenceCompat
+            android:key="bluetooth_leaudio_broadcast_ui"
+            android:title="@string/bluetooth_leaudio_broadcast_ui"
+            android:summary="@string/bluetooth_leaudio_broadcast_ui_summary"/>
+
+        <SwitchPreferenceCompat
             android:key="bluetooth_show_leaudio_device_details"
             android:title="@string/bluetooth_show_leaudio_device_details"/>
 
diff --git a/res/xml/language_settings.xml b/res/xml/language_settings.xml
index 4613cb0..7618399 100644
--- a/res/xml/language_settings.xml
+++ b/res/xml/language_settings.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:title="@string/languages_settings"
-    android:key="languages_settings">
+    android:key="language_settings">
     <PreferenceCategory
         android:key="languages_category"
         android:title="@string/locale_picker_category_title">
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 481ad65..adda094 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -1516,13 +1516,13 @@
             final UserManager userManager = context.getSystemService(
                     UserManager.class);
             final int status = biometricManager.canAuthenticate(getEffectiveUserId(
-                    userManager, userId), BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+                    userManager, userId), BiometricManager.Authenticators.IDENTITY_CHECK);
             switch(status) {
                 case BiometricManager.BIOMETRIC_SUCCESS:
                     return BiometricStatus.OK;
                 case BiometricManager.BIOMETRIC_ERROR_LOCKOUT:
                     return BiometricStatus.LOCKOUT;
-                case BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
+                case BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE:
                 case BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS:
                     return BiometricStatus.NOT_ACTIVE;
                 default:
@@ -1582,7 +1582,7 @@
         final Intent intent = new Intent();
         if (android.hardware.biometrics.Flags.mandatoryBiometrics()) {
             intent.putExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
-                    BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+                    BiometricManager.Authenticators.IDENTITY_CHECK);
         }
         intent.putExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT,
                 resources.getString(R.string.cancel));
diff --git a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
index 50ac3cd..030aadb 100644
--- a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
@@ -45,6 +45,8 @@
 import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollStageCountInteractor
+import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollStageThresholdInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
@@ -113,6 +115,12 @@
   fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
     CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
 
+  fun createFingerprintEnrollStageCountInteractor(): FingerprintEnrollStageCountInteractor =
+    FingerprintEnrollStageCountInteractor(fingerprintEnrollmentRepository)
+
+  fun createFingerprintEnrollStageThresholdInteractor(): FingerprintEnrollStageThresholdInteractor =
+    FingerprintEnrollStageThresholdInteractor(fingerprintEnrollmentRepository)
+
   fun createGenerateChallengeInteractor(): GenerateChallengeInteractor =
     GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider)
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt
index 0bb4eea..d01a49c 100644
--- a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt
@@ -46,6 +46,16 @@
   /** Indicates if a user can enroll another fingerprint */
   val canEnrollUser: Flow<Boolean>
 
+  val enrollStageCount: Int
+
+  /**
+   * Returns the threshold for the given stage of fingerprint enrollment.
+   *
+   * @param index The index of the enrollment stage.
+   * @return The threshold for the enrollment stage.
+   */
+  fun getEnrollStageThreshold(index: Int): Float
+
   /**
    * Indicates if we should use the default settings for maximum enrollments or the sensor props
    * from the fingerprint sensor
@@ -115,4 +125,10 @@
       ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
       ?.toList()
   }
+
+  override val enrollStageCount: Int
+    get() = fingerprintManager.enrollStageCount
+
+  override fun getEnrollStageThreshold(index: Int): Float =
+    fingerprintManager.getEnrollStageThreshold(index)
 }
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollStageCountInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollStageCountInteractor.kt
new file mode 100644
index 0000000..6148158
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollStageCountInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.fingerprint2.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
+
+/**
+ * Interactor class for retrieving the total number of enrollment stages.
+ *
+ * This class interacts with the `FingerprintsRepository` to obtain the count
+ * of stages involved in the fingerprint enrollment process.
+ */
+class FingerprintEnrollStageCountInteractor (
+    fingerprintEnrollmentRepository: FingerprintEnrollmentRepository
+) {
+    /** The total number of enrollment stages. */
+    val count: Int = fingerprintEnrollmentRepository.enrollStageCount
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollStageThresholdInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollStageThresholdInteractor.kt
new file mode 100644
index 0000000..a96e4ef
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollStageThresholdInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.fingerprint2.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
+
+/**
+ * Interactor class for retrieving the enrollment stage threshold.
+ *
+ * This class interacts with the `fingerprintEnrollmentRepository` to fetch the threshold value
+ * for a specific enrollment stage.
+ */
+class FingerprintEnrollStageThresholdInteractor(
+    private val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository,
+) {
+    /**
+     * Retrieves the enrollment stage threshold for the given index.
+     *
+     * @param index The index of the enrollment stage.
+     * @return The threshold value for the specified stage.
+     */
+    fun getThreshold(index: Int): Float = fingerprintEnrollmentRepository.getEnrollStageThreshold(index)
+}
\ No newline at end of file
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 2860ce8..a1a22be 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -123,6 +123,14 @@
                         }
                     }
                 }
+
+                @Override
+                public void onDeviceBondStateChanged(
+                        @NonNull CachedBluetoothDevice cachedDevice, int bondState) {
+                    if (cachedDevice.equals(mCachedDevice)) {
+                        finishFragmentIfNecessary();
+                    }
+                }
             };
 
     private final BluetoothAdapter.OnMetadataChangedListener mExtraControlMetadataListener =
diff --git a/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
index 4b91716a..cade566 100644
--- a/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
+++ b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
@@ -30,10 +30,13 @@
 import com.android.settingslib.media.domain.interactor.SpatializerInteractor
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
@@ -41,9 +44,7 @@
 /** Provides device setting for spatial audio. */
 interface SpatialAudioInteractor {
     /** Gets device setting for spatial audio */
-    fun getDeviceSetting(
-        cachedDevice: CachedBluetoothDevice,
-    ): Flow<DeviceSettingModel?>
+    fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?>
 }
 
 class SpatialAudioInteractorImpl(
@@ -56,33 +57,55 @@
     private val spatialAudioOffToggle =
         ToggleModel(
             context.getString(R.string.spatial_audio_multi_toggle_off),
-            DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off))
+            DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off),
+        )
     private val spatialAudioOnToggle =
         ToggleModel(
             context.getString(R.string.spatial_audio_multi_toggle_on),
-            DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio))
+            DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio),
+        )
     private val headTrackingOnToggle =
         ToggleModel(
             context.getString(R.string.spatial_audio_multi_toggle_head_tracking_on),
-            DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking))
+            DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking),
+        )
     private val changes = MutableSharedFlow<Unit>()
 
-    override fun getDeviceSetting(
-        cachedDevice: CachedBluetoothDevice,
-    ): Flow<DeviceSettingModel?> =
+    override fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?> =
         changes
             .onStart { emit(Unit) }
-            .map { getSpatialAudioDeviceSettingModel(cachedDevice) }
+            .combine(
+                isDeviceConnected(cachedDevice),
+            ) { _, connected ->
+                if (connected) {
+                    getSpatialAudioDeviceSettingModel(cachedDevice)
+                } else {
+                    null
+                }
+            }
+            .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = null)
 
+    private fun isDeviceConnected(cachedDevice: CachedBluetoothDevice): Flow<Boolean> =
+        callbackFlow {
+                val listener =
+                    CachedBluetoothDevice.Callback { launch { send(cachedDevice.isConnected) } }
+                cachedDevice.registerCallback(context.mainExecutor, listener)
+                awaitClose { cachedDevice.unregisterCallback(listener) }
+            }
+            .onStart { emit(cachedDevice.isConnected) }
+            .flowOn(backgroundCoroutineContext)
+
     private suspend fun getSpatialAudioDeviceSettingModel(
-        cachedDevice: CachedBluetoothDevice,
+        cachedDevice: CachedBluetoothDevice
     ): DeviceSettingModel? {
         // TODO(b/343317785): use audio repository instead of calling AudioManager directly.
         Log.i(TAG, "CachedDevice: $cachedDevice profiles: ${cachedDevice.profiles}")
         val attributes =
             BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
-                cachedDevice, audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address))
+                cachedDevice,
+                audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address),
+            )
                 ?: run {
                     Log.i(TAG, "No audio profiles in cachedDevice: ${cachedDevice.address}.")
                     return null
@@ -116,7 +139,8 @@
             TAG,
             "Head tracking available: $headTrackingAvailable, " +
                 "spatial audio enabled: $spatialAudioEnabled, " +
-                "head tracking enabled: $headTrackingEnabled")
+                "head tracking enabled: $headTrackingEnabled",
+        )
         return DeviceSettingModel.MultiTogglePreference(
             cachedDevice = cachedDevice,
             id = DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE,
@@ -143,7 +167,8 @@
                     }
                     changes.emit(Unit)
                 }
-            })
+            },
+        )
     }
 
     companion object {
diff --git a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java
index 739258d..bf5efa7 100644
--- a/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java
+++ b/src/com/android/settings/development/BluetoothLeAudioModePreferenceController.java
@@ -34,12 +34,10 @@
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
+import java.util.Objects;
 
-/**
- * Preference controller to control Bluetooth LE audio mode
- */
-public class BluetoothLeAudioModePreferenceController
-        extends DeveloperOptionsPreferenceController
+/** Preference controller to control Bluetooth LE audio mode */
+public class BluetoothLeAudioModePreferenceController extends DeveloperOptionsPreferenceController
         implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
 
     private static final String PREFERENCE_KEY = "bluetooth_leaudio_mode";
@@ -51,15 +49,13 @@
 
     private final String[] mListValues;
     private final String[] mListSummaries;
-    @VisibleForTesting
-    @Nullable String mNewMode;
-    @VisibleForTesting
-    BluetoothAdapter mBluetoothAdapter;
+    @VisibleForTesting @Nullable String mNewMode;
+    @VisibleForTesting BluetoothAdapter mBluetoothAdapter;
 
     boolean mChanged = false;
 
-    public BluetoothLeAudioModePreferenceController(@NonNull Context context,
-            @Nullable DevelopmentSettingsDashboardFragment fragment) {
+    public BluetoothLeAudioModePreferenceController(
+            @NonNull Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
         super(context);
         mFragment = fragment;
         mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
@@ -69,7 +65,8 @@
     }
 
     @Override
-    @NonNull public String getPreferenceKey() {
+    @NonNull
+    public String getPreferenceKey() {
         return PREFERENCE_KEY;
     }
 
@@ -125,20 +122,25 @@
         }
     }
 
-    /**
-     * Called when the RebootDialog confirm is clicked.
-     */
+    /** Called when the RebootDialog confirm is clicked. */
     public void onRebootDialogConfirmed() {
         if (!mChanged) {
             return;
         }
         SystemProperties.set(LE_AUDIO_DYNAMIC_SWITCHER_MODE_PROPERTY, mNewMode);
+        if (mFragment != null && !Objects.equals(mNewMode, "broadcast")) {
+            mFragment.onBroadcastDisabled();
+        }
     }
 
-    /**
-     * Called when the RebootDialog cancel is clicked.
-     */
+    /** Called when the RebootDialog cancel is clicked. */
     public void onRebootDialogCanceled() {
         mChanged = false;
     }
+
+    public interface OnModeChangeListener {
+
+        /** Called when the broadcast mode is disabled. */
+        void onBroadcastDisabled();
+    }
 }
diff --git a/src/com/android/settings/development/BluetoothLeAudioUiPreferenceController.java b/src/com/android/settings/development/BluetoothLeAudioUiPreferenceController.java
new file mode 100644
index 0000000..f2ae55f
--- /dev/null
+++ b/src/com/android/settings/development/BluetoothLeAudioUiPreferenceController.java
@@ -0,0 +1,144 @@
+/*
+ * 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.development;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.sysprop.BluetoothProperties;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+import com.android.settingslib.flags.Flags;
+import com.android.settingslib.utils.ThreadUtils;
+
+/** Preference controller to enable / disable the Bluetooth LE audio sharing UI flow */
+public class BluetoothLeAudioUiPreferenceController extends DeveloperOptionsPreferenceController
+        implements Preference.OnPreferenceChangeListener,
+                PreferenceControllerMixin,
+                BluetoothLeAudioModePreferenceController.OnModeChangeListener {
+    private static final String TAG = "BluetoothLeAudioUiPreferenceController";
+    private static final String PREFERENCE_KEY = "bluetooth_leaudio_broadcast_ui";
+
+    @VisibleForTesting
+    static final String VALUE_KEY = "bluetooth_le_audio_sharing_ui_preview_enabled";
+
+    @VisibleForTesting static final int VALUE_OFF = 0;
+    @VisibleForTesting static final int VALUE_ON = 1;
+    @VisibleForTesting static final int VALUE_UNSET = -1;
+    @Nullable private final DevelopmentSettingsDashboardFragment mFragment;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private boolean mCurrentSettingsValue = false;
+    private boolean mShouldToggleCurrentValue = false;
+
+    public BluetoothLeAudioUiPreferenceController(
+            @NonNull Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) {
+        super(context);
+        mFragment = fragment;
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return Flags.audioSharingDeveloperOption()
+                && BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false)
+                && BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
+    }
+
+    @Override
+    public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
+        if (mFragment != null && newValue != null && (boolean) newValue != mCurrentSettingsValue) {
+            mShouldToggleCurrentValue = true;
+            BluetoothRebootDialog.show(mFragment);
+        }
+        return false;
+    }
+
+    @Override
+    public void updateState(@NonNull Preference preference) {
+        if (mBluetoothAdapter == null) {
+            return;
+        }
+        var unused = ThreadUtils.postOnBackgroundThread(
+                () -> {
+                    boolean shouldEnable =
+                            mBluetoothAdapter.isEnabled()
+                                    && mBluetoothAdapter.isLeAudioBroadcastSourceSupported()
+                                            == BluetoothStatusCodes.FEATURE_SUPPORTED
+                                    && mBluetoothAdapter.isLeAudioBroadcastAssistantSupported()
+                                            == BluetoothStatusCodes.FEATURE_SUPPORTED;
+                    boolean valueOn =
+                            Settings.Global.getInt(
+                                            mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET)
+                                    == VALUE_ON;
+                    mContext.getMainExecutor()
+                            .execute(
+                                    () -> {
+                                        if (!shouldEnable && valueOn) {
+                                            Log.e(
+                                                    TAG,
+                                                    "Error state: toggle disabled but current"
+                                                            + " settings value is true.");
+                                        }
+                                        mCurrentSettingsValue = valueOn;
+                                        preference.setEnabled(shouldEnable);
+                                        ((SwitchPreferenceCompat) preference).setChecked(valueOn);
+                                    });
+                });
+    }
+
+    @Override
+    public @NonNull String getPreferenceKey() {
+        return PREFERENCE_KEY;
+    }
+
+    /** Called when the RebootDialog confirm is clicked. */
+    public void onRebootDialogConfirmed() {
+        if (isAvailable() && mShouldToggleCurrentValue) {
+            // Blocking, ensure reboot happens after value is saved.
+            Log.d(TAG, "onRebootDialogConfirmed(): setting value to " + !mCurrentSettingsValue);
+            toggleSetting(mContext.getContentResolver(), !mCurrentSettingsValue);
+        }
+    }
+
+    /** Called when the RebootDialog cancel is clicked. */
+    public void onRebootDialogCanceled() {
+        mShouldToggleCurrentValue = false;
+    }
+
+    @Override
+    public void onBroadcastDisabled() {
+        if (isAvailable() && mCurrentSettingsValue) {
+            Log.d(TAG, "onBroadcastDisabled(): setting value to false");
+            // Blocking, ensure reboot happens after value is saved.
+            toggleSetting(mContext.getContentResolver(), false);
+        }
+    }
+
+    private static void toggleSetting(ContentResolver contentResolver, boolean valueOn) {
+        Settings.Global.putInt(contentResolver, VALUE_KEY, valueOn ? VALUE_ON : VALUE_OFF);
+    }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 8a970fb..b453de1 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -99,7 +99,9 @@
         AdbClearKeysDialogHost, LogPersistDialogHost,
         BluetoothRebootDialog.OnRebootDialogListener,
         AbstractBluetoothPreferenceController.Callback,
-        NfcRebootDialog.OnNfcRebootDialogConfirmedListener, BluetoothSnoopLogHost {
+        NfcRebootDialog.OnNfcRebootDialogConfirmedListener,
+        BluetoothSnoopLogHost,
+        BluetoothLeAudioModePreferenceController.OnModeChangeListener {
 
     private static final String TAG = "DevSettingsDashboard";
     @VisibleForTesting static final int REQUEST_BIOMETRIC_PROMPT = 100;
@@ -498,6 +500,10 @@
                 getDevelopmentOptionsController(
                         BluetoothLeAudioModePreferenceController.class);
         leAudioModeController.onRebootDialogConfirmed();
+
+        final BluetoothLeAudioUiPreferenceController leAudioUiController =
+                getDevelopmentOptionsController(BluetoothLeAudioUiPreferenceController.class);
+        leAudioUiController.onRebootDialogConfirmed();
     }
 
     @Override
@@ -520,6 +526,10 @@
                 getDevelopmentOptionsController(
                         BluetoothLeAudioModePreferenceController.class);
         leAudioModeController.onRebootDialogCanceled();
+
+        final BluetoothLeAudioUiPreferenceController leAudioUiController =
+                getDevelopmentOptionsController(BluetoothLeAudioUiPreferenceController.class);
+        leAudioUiController.onRebootDialogCanceled();
     }
 
     @Override
@@ -741,6 +751,7 @@
         controllers.add(new BluetoothMapVersionPreferenceController(context));
         controllers.add(new BluetoothLeAudioPreferenceController(context, fragment));
         controllers.add(new BluetoothLeAudioModePreferenceController(context, fragment));
+        controllers.add(new BluetoothLeAudioUiPreferenceController(context, fragment));
         controllers.add(new BluetoothLeAudioDeviceDetailsPreferenceController(context));
         controllers.add(new BluetoothLeAudioAllowListPreferenceController(context));
         controllers.add(new BluetoothA2dpHwOffloadPreferenceController(context, fragment));
@@ -858,6 +869,15 @@
         }
     }
 
+    @Override
+    public void onBroadcastDisabled() {
+        for (AbstractPreferenceController controller : mPreferenceControllers) {
+            if (controller instanceof BluetoothLeAudioUiPreferenceController) {
+                ((BluetoothLeAudioUiPreferenceController) controller).onBroadcastDisabled();
+            }
+        }
+    }
+
     /**
      * For Search.
      */
diff --git a/src/com/android/settings/language/LanguageSettingScreen.kt b/src/com/android/settings/language/LanguageSettingScreen.kt
new file mode 100644
index 0000000..09ca11b
--- /dev/null
+++ b/src/com/android/settings/language/LanguageSettingScreen.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.language
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.settings.R
+import com.android.settings.Settings.LanguageSettingsActivity
+import com.android.settings.flags.Flags
+import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.ProvidePreferenceScreen
+import com.android.settingslib.metadata.preferenceHierarchy
+import com.android.settingslib.preference.PreferenceScreenCreator
+
+@ProvidePreferenceScreen
+class LanguageSettingScreen: PreferenceScreenCreator {
+    override val key: String
+        get() = KEY
+
+    override val title: Int
+        get() = R.string.languages_settings
+
+    override val summary: Int
+        get() = R.string.languages_setting_summary
+
+    override val icon: Int
+        get() = R.drawable.ic_settings_languages
+
+    override fun isFlagEnabled(context: Context) = Flags.catalystLanguageSetting()
+
+    override fun hasCompleteHierarchy() = false
+
+    override fun fragmentClass() = LanguageSettings::class.java
+
+    override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {}
+
+    companion object {
+        const val KEY = "language_setting"
+    }
+}
diff --git a/src/com/android/settings/language/LanguageSettings.java b/src/com/android/settings/language/LanguageSettings.java
index a5adb02..d992ff2 100644
--- a/src/com/android/settings/language/LanguageSettings.java
+++ b/src/com/android/settings/language/LanguageSettings.java
@@ -67,6 +67,11 @@
     }
 
     @Override
+    public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
+        return LanguageSettingScreen.KEY;
+    }
+
+    @Override
     protected int getPreferenceScreenResId() {
         return R.xml.language_settings;
     }
diff --git a/src/com/android/settings/notification/CallVolumePreference.kt b/src/com/android/settings/notification/CallVolumePreference.kt
new file mode 100644
index 0000000..9b593ad
--- /dev/null
+++ b/src/com/android/settings/notification/CallVolumePreference.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.notification
+
+import android.content.Context
+import android.media.AudioManager
+import android.media.AudioManager.STREAM_BLUETOOTH_SCO
+import android.media.AudioManager.STREAM_VOICE_CALL
+import android.os.UserHandle
+import android.os.UserManager.DISALLOW_ADJUST_VOLUME
+import androidx.preference.Preference
+import com.android.settings.R
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.datastore.KeyValueStore
+import com.android.settingslib.datastore.NoOpKeyedObservable
+import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceIconProvider
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.metadata.PreferenceRestrictionProvider
+import com.android.settingslib.metadata.RangeValue
+import com.android.settingslib.preference.PreferenceBinding
+
+// LINT.IfChange
+open class CallVolumePreference :
+    PreferenceMetadata,
+    PreferenceBinding,
+    PersistentPreference<Int>,
+    RangeValue,
+    PreferenceAvailabilityProvider,
+    PreferenceIconProvider,
+    PreferenceRestrictionProvider {
+    override val key: String
+        get() = KEY
+
+    override val title: Int
+        get() = R.string.call_volume_option_title
+
+    override fun getIcon(context: Context) = R.drawable.ic_local_phone_24_lib
+
+    override fun order(context: Context) = -170
+
+    override fun isAvailable(context: Context) =
+        context.resources.getBoolean(R.bool.config_show_call_volume) &&
+                !createAudioHelper(context).isSingleVolume()
+
+    override fun isRestricted(context: Context) =
+        RestrictedLockUtilsInternal.hasBaseUserRestriction(
+            context,
+            DISALLOW_ADJUST_VOLUME,
+            UserHandle.myUserId()
+        ) || RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+            context,
+            DISALLOW_ADJUST_VOLUME,
+            UserHandle.myUserId()
+        ) != null
+
+    override fun storage(context: Context): KeyValueStore {
+        val helper = createAudioHelper(context)
+        return object : NoOpKeyedObservable<String>(), KeyValueStore {
+            override fun contains(key: String) = key == KEY
+
+            @Suppress("UNCHECKED_CAST")
+            override fun <T : Any> getValue(key: String, valueType: Class<T>) =
+                helper.getStreamVolume(getAudioStream(context)) as T
+
+            override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
+                helper.setStreamVolume(getAudioStream(context), value as Int)
+            }
+        }
+    }
+
+    override fun getMinValue(context: Context) =
+        createAudioHelper(context).getMinVolume(getAudioStream(context))
+
+    override fun getMaxValue(context: Context) =
+        createAudioHelper(context).getMaxVolume(getAudioStream(context))
+
+    override fun createWidget(context: Context) = VolumeSeekBarPreference(context)
+
+    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
+        super.bind(preference, metadata)
+        (preference as VolumeSeekBarPreference).setStream(getAudioStream(preference.context))
+    }
+
+    open fun createAudioHelper(context: Context) = AudioHelper(context)
+
+    @Suppress("DEPRECATION")
+    fun getAudioStream(context: Context): Int {
+        val audioManager = context.getSystemService(AudioManager::class.java)
+        return when {
+            audioManager.isBluetoothScoOn() -> STREAM_BLUETOOTH_SCO
+            else -> STREAM_VOICE_CALL
+        }
+    }
+
+    companion object {
+        const val KEY = "call_volume"
+    }
+}
+// LINT.ThenChange(CallVolumePreferenceController.java)
diff --git a/src/com/android/settings/notification/CallVolumePreferenceController.java b/src/com/android/settings/notification/CallVolumePreferenceController.java
index d505227..d7c5ddc 100644
--- a/src/com/android/settings/notification/CallVolumePreferenceController.java
+++ b/src/com/android/settings/notification/CallVolumePreferenceController.java
@@ -22,7 +22,7 @@
 
 import com.android.settings.R;
 
-
+// LINT.IfChange
 public class CallVolumePreferenceController extends VolumeSeekBarPreferenceController {
 
     private AudioManager mAudioManager;
@@ -69,3 +69,4 @@
     }
 
 }
+// LINT.ThenChange(CallVolumePreference.kt)
diff --git a/src/com/android/settings/notification/SoundScreen.kt b/src/com/android/settings/notification/SoundScreen.kt
index 6b60967..f29437b 100644
--- a/src/com/android/settings/notification/SoundScreen.kt
+++ b/src/com/android/settings/notification/SoundScreen.kt
@@ -49,6 +49,7 @@
 
     override fun getPreferenceHierarchy(context: Context) =
         preferenceHierarchy(this) {
+            +CallVolumePreference()
             +DialPadTonePreference()
         }
 
diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java
index b069c7e..fffb784 100644
--- a/src/com/android/settings/notification/SoundSettings.java
+++ b/src/com/android/settings/notification/SoundSettings.java
@@ -113,6 +113,14 @@
         if (phoneRingTonePreference != null && openPhoneRingtonePicker) {
             onPreferenceTreeClick(phoneRingTonePreference);
         }
+        if (isCatalystEnabled()) {
+            for (String key : getPreferenceKeysInHierarchy()) {
+                Preference preference = findPreference(key);
+                if (preference instanceof VolumeSeekBarPreference) {
+                    ((VolumeSeekBarPreference) preference).setCallback(mVolumeCallback);
+                }
+            }
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/notification/app/BundleListPreferenceController.java b/src/com/android/settings/notification/app/BundleListPreferenceController.java
index 82e910c..9ada049 100644
--- a/src/com/android/settings/notification/app/BundleListPreferenceController.java
+++ b/src/com/android/settings/notification/app/BundleListPreferenceController.java
@@ -91,14 +91,27 @@
     public void updateState(Preference preference) {
         PreferenceCategory category = (PreferenceCategory) preference;
 
-        createOrUpdatePrefForChannel(category,
-                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID));
-        createOrUpdatePrefForChannel(category,
-                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID));
-        createOrUpdatePrefForChannel(category,
-                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID));
-        createOrUpdatePrefForChannel(category,
-                mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID));
+        NotificationChannel promos = mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID);
+        if (promos != null) {
+            createOrUpdatePrefForChannel(category, promos);
+        }
+        NotificationChannel recs = mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID);
+        if (recs != null) {
+            createOrUpdatePrefForChannel(category, recs);
+        }
+        NotificationChannel social = mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID);
+        if (social != null) {
+            createOrUpdatePrefForChannel(category, social);
+        }
+        NotificationChannel news = mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID);
+        if (news != null) {
+            createOrUpdatePrefForChannel(category, news);
+        }
+
+        int preferenceCount = ((PreferenceGroup) preference).getPreferenceCount();
+        if (preferenceCount == 0) {
+            preference.setVisible(false);
+        }
     }
 
     @NonNull
@@ -167,5 +180,4 @@
         icon.setTintList(Utils.getColorAccent(mContext));
         return icon;
     }
-
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
index 014a190..13d5c6e 100644
--- a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
@@ -40,6 +40,7 @@
 import androidx.preference.Preference;
 
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settingslib.PrimarySwitchPreference;
 import com.android.settingslib.notification.modes.ZenMode;
 import com.android.settingslib.notification.modes.ZenModesBackend;
@@ -108,7 +109,9 @@
                     tryParseScheduleConditionId(mode.getRule().getConditionId());
             if (schedule != null) {
                 preference.setTitle(SystemZenRules.getTimeSummary(mContext, schedule));
-                preference.setSummary(SystemZenRules.getShortDaysSummary(mContext, schedule));
+                preference.setSummary(Utils.createAccessibleSequence(
+                        SystemZenRules.getDaysOfWeekShort(mContext, schedule),
+                        SystemZenRules.getDaysOfWeekFull(mContext, schedule)));
             } else {
                 // Fallback, but shouldn't happen.
                 Log.wtf(TAG, "SCHEDULE_TIME mode without schedule: " + mode);
diff --git a/tests/robotests/src/com/android/settings/MainClearTest.java b/tests/robotests/src/com/android/settings/MainClearTest.java
index 0f823d6..4c2b266 100644
--- a/tests/robotests/src/com/android/settings/MainClearTest.java
+++ b/tests/robotests/src/com/android/settings/MainClearTest.java
@@ -149,8 +149,8 @@
         doReturn(mMockActivity).when(mMainClear).getActivity();
         when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
-                .thenReturn(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
+                .thenReturn(BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE);
     }
 
     @After
@@ -379,7 +379,7 @@
         when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
         when(mResources.getString(anyInt())).thenReturn(TEST_ACCOUNT_NAME);
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
         doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
         doNothing().when(mMainClear).startActivityForResult(any(), anyInt());
@@ -406,7 +406,7 @@
         when(mMockActivity.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
         when(mResources.getString(anyInt())).thenReturn(TEST_ACCOUNT_NAME);
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_LOCKOUT);
         doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
         doNothing().when(mMainClear).startActivityForResult(any(), anyInt());
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index 8f4b83e..b91ad6d 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -541,7 +541,7 @@
     @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testRequestBiometricAuthentication_biometricManagerReturnsSuccess_shouldReturnOk() {
         when(mBiometricManager.canAuthenticate(USER_ID,
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                BiometricManager.Authenticators.IDENTITY_CHECK))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
         final Utils.BiometricStatus requestBiometricAuthenticationForMandatoryBiometrics =
                 Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
@@ -554,7 +554,7 @@
     @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testRequestBiometricAuthentication_biometricManagerReturnsError_shouldReturnError() {
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
                 false /* biometricsAuthenticationRequested */, USER_ID)).isEqualTo(
@@ -567,10 +567,10 @@
         when(mContext.getSystemService(UserManager.class)).thenReturn(mMockUserManager);
         when(mMockUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(USER_ID);
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         when(mBiometricManager.canAuthenticate(0 /* userId */,
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                BiometricManager.Authenticators.IDENTITY_CHECK))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
         assertThat(Utils.requestBiometricAuthenticationForMandatoryBiometrics(mContext,
                 false /* biometricsAuthenticationRequested */, USER_ID)).isEqualTo(
@@ -594,7 +594,7 @@
         final Intent intent = intentArgumentCaptor.getValue();
 
         assertThat(intent.getExtra(BIOMETRIC_PROMPT_AUTHENTICATORS)).isEqualTo(
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+                BiometricManager.Authenticators.IDENTITY_CHECK);
         assertThat(intent.getExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT)).isNotNull();
         assertThat(intent.getExtra(KeyguardManager.EXTRA_DESCRIPTION)).isNotNull();
         assertThat(intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, false))
diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
index b4605c7..3dc011e 100644
--- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java
@@ -128,8 +128,8 @@
         doReturn(mActivity).when(mFragment).getActivity();
         doReturn(mBiometricManager).when(mActivity).getSystemService(BiometricManager.class);
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
-                .thenReturn(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
+                .thenReturn(BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE);
 
         ReflectionHelpers.setField(mFragment, "mDashboardFeatureProvider",
                 FakeFeatureFactory.setupForTest().dashboardFeatureProvider);
@@ -182,7 +182,7 @@
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         doNothing().when(mFragment).startActivityForResult(any(), anyInt());
         when(mBiometricManager.canAuthenticate(anyInt(),
-                eq(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)))
+                eq(BiometricManager.Authenticators.IDENTITY_CHECK)))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
 
         mFragment.onAttach(mContext);
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
index 0e1bcf6..1086f85 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
@@ -149,7 +149,7 @@
         doReturn(true).when(mFingerprintManager).isHardwareDetected();
         doReturn(mVibrator).when(mContext).getSystemService(Vibrator.class);
         when(mBiometricManager.canAuthenticate(PRIMARY_USER_ID,
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                BiometricManager.Authenticators.IDENTITY_CHECK))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
     }
 
@@ -176,7 +176,7 @@
     @EnableFlags(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testLaunchBiometricPromptForFingerprint() {
         when(mBiometricManager.canAuthenticate(PRIMARY_USER_ID,
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                BiometricManager.Authenticators.IDENTITY_CHECK))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
         doNothing().when(mFingerprintManager).generateChallenge(anyInt(), any());
         when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
index fc19728..9609af4 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
@@ -40,6 +40,7 @@
 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -84,6 +85,7 @@
     @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
     @Mock private LocalBluetoothProfileManager mProfileManager;
     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+    @Mock private LocalBluetoothLeBroadcast mBroadcast;
     @Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState;
 
     private Context mContext;
@@ -102,6 +104,7 @@
         ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
         mLocalBtManager = Utils.getLocalBtManager(mContext);
         when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+        when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
         when(mLocalBtManager.getProfileManager()).thenReturn(mProfileManager);
         when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
@@ -267,8 +270,7 @@
     }
 
     @Test
-    public void
-            onProfileConnectionStateChanged_leaConnected_notInCallSharingFlagOff_addsPreference() {
+    public void onProfileConnectionStateChanged_leaConnected_notInCallSharingFlagOff_addPref() {
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
@@ -288,8 +290,7 @@
     }
 
     @Test
-    public void
-            onProfileConnectionStateChanged_leaConnected_notInCallNotInSharing_addsPreference() {
+    public void onProfileConnectionStateChanged_leaConnected_notInCallNotInSharing_addPref() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
@@ -306,16 +307,13 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaConnected_inCallSharingFlagOff_addsPreference() {
+    public void onProfileConnectionStateChanged_leaConnected_inCallSharingFlagOff_addPref() {
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
                 .thenReturn(true);
         when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
-        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mBroadcastReceiveState));
-        List<Long> bisSyncState = new ArrayList<>();
-        bisSyncState.add(1L);
-        when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of());
 
         mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
                 mCachedBluetoothDevice,
@@ -326,7 +324,7 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaConnected_inCallNotInSharing_addsPreference() {
+    public void onProfileConnectionStateChanged_leaConnected_inCallNotInSharing_addPref() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         mAudioManager.setMode(AudioManager.MODE_IN_CALL);
         when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
@@ -343,9 +341,9 @@
     }
 
     @Test
-    public void
-            onProfileConnectionStateChanged_leaDeviceConnected_notInCallInSharing_removesPref() {
+    public void onProfileConnectionStateChanged_leaConnected_notInCallInSharing_removePref() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
                 .thenReturn(true);
@@ -365,8 +363,31 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaDeviceConnected_inCallInSharing_removesPref() {
+    public void
+            onProfileConnectionStateChanged_leaConnected_noInCallInSharing_hysteresis_removePref() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
+                .thenReturn(true);
+        when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mBroadcastReceiveState));
+        when(mBroadcast.getLatestBroadcastId()).thenReturn(1);
+        when(mBroadcastReceiveState.getBroadcastId()).thenReturn(1);
+
+        mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.LE_AUDIO);
+
+        verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaConnected_inCallSharing_removePref() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
                 .thenReturn(true);
@@ -386,6 +407,27 @@
     }
 
     @Test
+    public void onProfileConnectionStateChanged_leaConnected_inCallSharing_hysteresis_removePref() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
+                .thenReturn(true);
+        when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
+        when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mBroadcastReceiveState));
+        when(mBroadcast.getLatestBroadcastId()).thenReturn(1);
+        when(mBroadcastReceiveState.getBroadcastId()).thenReturn(1);
+
+        mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.LE_AUDIO);
+
+        verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
+    }
+
+    @Test
     public void
             onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovePreference() {
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt b/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
index a83b7c2..28e0581 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
@@ -83,6 +83,7 @@
     @Test
     fun getDeviceSetting_noAudioProfile_returnNull() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
 
             assertThat(setting).isNull()
@@ -93,6 +94,7 @@
     @Test
     fun getDeviceSetting_audioProfileNotEnabled_returnNull() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
             `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false)
 
@@ -104,8 +106,23 @@
     }
 
     @Test
+    fun getDeviceSetting_deviceNotConnected_returnNull() {
+        testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(false)
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+
+            val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+            assertThat(setting).isNull()
+            verifyNoInteractions(spatializerRepository)
+        }
+    }
+
+    @Test
     fun getDeviceSetting_spatialAudioNotSupported_returnNull() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
             `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
             `when`(
@@ -122,6 +139,7 @@
     @Test
     fun getDeviceSetting_spatialAudioSupported_returnTwoToggles() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
             `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
             `when`(
@@ -150,6 +168,7 @@
     @Test
     fun getDeviceSetting_headTrackingSupported_returnThreeToggles() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
             `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
             `when`(
@@ -178,6 +197,7 @@
     @Test
     fun getDeviceSetting_updateState_enableSpatialAudio() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
             `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
             `when`(
@@ -207,6 +227,7 @@
     @Test
     fun getDeviceSetting_updateState_enableHeadTracking() {
         testScope.runTest {
+            `when`(cachedDevice.isConnected).thenReturn(true)
             `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
             `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
             `when`(
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdaterTest.java
index 11e31b6..12e03d4 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdaterTest.java
@@ -51,6 +51,7 @@
 import com.android.settings.testutils.shadow.ShadowThreadUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -99,6 +100,7 @@
     @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
     @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
     @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+    @Mock private LocalBluetoothLeBroadcast mBroadcast;
     @Mock private BluetoothLeBroadcastReceiveState mState;
 
     private Context mContext;
@@ -122,9 +124,7 @@
         when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
         when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
         when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
-        List<Long> bisSyncState = new ArrayList<>();
-        bisSyncState.add(1L);
-        when(mState.getBisSyncState()).thenReturn(bisSyncState);
+        when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
         Pair<Drawable, String> pairs = new Pair<>(mDrawable, TEST_DEVICE_NAME);
         doReturn(TEST_DEVICE_NAME).when(mCachedBluetoothDevice).getName();
         doReturn(mBluetoothDevice).when(mCachedBluetoothDevice).getDevice();
@@ -152,7 +152,7 @@
 
     @Test
     public void onProfileConnectionStateChanged_leaDeviceConnected_flagOff_removesPref() {
-        setupPreferenceMapWithDevice();
+        setupPreferenceMapWithDevice(false);
 
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
@@ -170,8 +170,46 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaDeviceConnected_noSource_removesPref() {
-        setupPreferenceMapWithDevice();
+    public void onProfileConnectionStateChanged_leaConnected_flagOff_hysteresisMode_removesPref() {
+        setupPreferenceMapWithDevice(true);
+
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
+
+        mDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
+        assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
+        assertThat(((BluetoothDevicePreference) captor.getValue()).getBluetoothDevice())
+                .isEqualTo(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaConnected_noSource_removesPref() {
+        setupPreferenceMapWithDevice(false);
+
+        when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of());
+        ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
+
+        mDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
+        assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
+        assertThat(((BluetoothDevicePreference) captor.getValue()).getBluetoothDevice())
+                .isEqualTo(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaConnected_noSource_hysteresisMode_removesPref() {
+        setupPreferenceMapWithDevice(true);
 
         when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of());
         ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
@@ -190,7 +228,7 @@
 
     @Test
     public void onProfileConnectionStateChanged_deviceIsNotInList_removesPref() {
-        setupPreferenceMapWithDevice();
+        setupPreferenceMapWithDevice(false);
 
         mCachedDevices.clear();
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
@@ -209,8 +247,28 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaDeviceDisconnected_removesPref() {
-        setupPreferenceMapWithDevice();
+    public void onProfileConnectionStateChanged_deviceIsNotInList_hysteresisMode_removesPref() {
+        setupPreferenceMapWithDevice(true);
+
+        mCachedDevices.clear();
+        when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
+        ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
+
+        mDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
+        assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
+        assertThat(((BluetoothDevicePreference) captor.getValue()).getBluetoothDevice())
+                .isEqualTo(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaDisconnected_removesPref() {
+        setupPreferenceMapWithDevice(false);
 
         when(mDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(false);
         ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
@@ -228,8 +286,27 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaDeviceDisconnecting_removesPref() {
-        setupPreferenceMapWithDevice();
+    public void onProfileConnectionStateChanged_leaDisconnected_hysteresisMode_removesPref() {
+        setupPreferenceMapWithDevice(true);
+
+        when(mDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(false);
+        ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
+
+        mDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
+        assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
+        assertThat(((BluetoothDevicePreference) captor.getValue()).getBluetoothDevice())
+                .isEqualTo(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaDisconnecting_removesPref() {
+        setupPreferenceMapWithDevice(false);
         doReturn(false).when(mCachedBluetoothDevice).isConnectedLeAudioDevice();
         ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
 
@@ -246,9 +323,38 @@
     }
 
     @Test
-    public void onProfileConnectionStateChanged_leaDeviceConnected_hasSource_addsPreference() {
+    public void onProfileConnectionStateChanged_leaDisconnecting_hysteresisMode_removesPref() {
+        setupPreferenceMapWithDevice(true);
+        doReturn(false).when(mCachedBluetoothDevice).isConnectedLeAudioDevice();
         ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
-        setupPreferenceMapWithDevice();
+
+        mDeviceUpdater.onProfileConnectionStateChanged(
+                mCachedBluetoothDevice,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.LE_AUDIO);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
+        assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
+        assertThat(((BluetoothDevicePreference) captor.getValue()).getBluetoothDevice())
+                .isEqualTo(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaConnected_hasSource_addsPref() {
+        ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
+        setupPreferenceMapWithDevice(false);
+
+        verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
+        assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
+        assertThat(((BluetoothDevicePreference) captor.getValue()).getBluetoothDevice())
+                .isEqualTo(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_leaConnected_hasSource_hysteresisMode_addsPref() {
+        ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
+        setupPreferenceMapWithDevice(true);
 
         verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
         assertThat(captor.getValue() instanceof BluetoothDevicePreference).isTrue();
@@ -275,9 +381,19 @@
         verify(mDevicePreferenceCallback).onDeviceClick(preference);
     }
 
-    private void setupPreferenceMapWithDevice() {
+    private void setupPreferenceMapWithDevice(boolean hysteresisModeOn) {
         // Add device to preferenceMap
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        if (hysteresisModeOn) {
+            mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+            when(mBroadcast.getLatestBroadcastId()).thenReturn(1);
+            when(mState.getBroadcastId()).thenReturn(1);
+        } else {
+            mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+            List<Long> bisSyncState = new ArrayList<>();
+            bisSyncState.add(1L);
+            when(mState.getBisSyncState()).thenReturn(bisSyncState);
+        }
         when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(mState));
         when(mDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
         doReturn(true).when(mCachedBluetoothDevice).isConnectedLeAudioDevice();
diff --git a/tests/robotests/src/com/android/settings/development/BluetoothLeAudioUiPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioUiPreferenceControllerTest.java
new file mode 100644
index 0000000..a4462e4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/BluetoothLeAudioUiPreferenceControllerTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.development;
+
+import static com.android.settings.development.BluetoothLeAudioUiPreferenceController.VALUE_KEY;
+import static com.android.settings.development.BluetoothLeAudioUiPreferenceController.VALUE_OFF;
+import static com.android.settings.development.BluetoothLeAudioUiPreferenceController.VALUE_ON;
+import static com.android.settings.development.BluetoothLeAudioUiPreferenceController.VALUE_UNSET;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+        shadows = {
+            ShadowBluetoothAdapter.class,
+            BluetoothLeAudioUiPreferenceControllerTest.ShadowBluetoothRebootDialogFragment.class
+        })
+public class BluetoothLeAudioUiPreferenceControllerTest {
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    private static final String SOURCE_SYSTEM_PROP_KEY =
+            "bluetooth.profile.bap.broadcast.source.enabled";
+    private static final String ASSIST_SYSTEM_PROP_KEY =
+            "bluetooth.profile.bap.broadcast.assist.enabled";
+    @Mock private PreferenceScreen mPreferenceScreen;
+    @Mock private DevelopmentSettingsDashboardFragment mFragment;
+    @Mock private SwitchPreferenceCompat mPreference;
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+    private Context mContext;
+    private BluetoothLeAudioUiPreferenceController mController;
+
+    @Before
+    public void setup() {
+        mContext = RuntimeEnvironment.getApplication();
+        SystemProperties.set(SOURCE_SYSTEM_PROP_KEY, "true");
+        SystemProperties.set(ASSIST_SYSTEM_PROP_KEY, "true");
+        // Reset value
+        Settings.Global.putInt(mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET);
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        mShadowBluetoothAdapter.setEnabled(true);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mController = spy(new BluetoothLeAudioUiPreferenceController(mContext, mFragment));
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+                .thenReturn(mPreference);
+        mController.displayPreference(mPreferenceScreen);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void isAvailable_flagOff_returnFalse() {
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void isAvailable_flagOn_returnFalse() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void isAvailable_flagOn_propertyOff_returnFalse() {
+        SystemProperties.set(SOURCE_SYSTEM_PROP_KEY, "false");
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void updateState_settingEnabled_checked() {
+        Settings.Global.putInt(mContext.getContentResolver(), VALUE_KEY, VALUE_ON);
+        mController.updateState(mPreference);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mPreference).setChecked(true);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void updateState_settingDisabled_notChecked() {
+        Settings.Global.putInt(mContext.getContentResolver(), VALUE_KEY, VALUE_OFF);
+        mController.updateState(mPreference);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mPreference).setChecked(false);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void updateState_featureSupported_enabled() {
+        mController.updateState(mPreference);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mPreference).setEnabled(true);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void updateState_featureUnsupported_disabled() {
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+        mController.updateState(mPreference);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mPreference).setEnabled(false);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void onRebootDialogConfirmed_noChange_doNothing() {
+        mController.onRebootDialogConfirmed();
+
+        int result = Settings.Global.getInt(mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET);
+        assertThat(result).isEqualTo(VALUE_UNSET);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void onRebootDialogConfirmed_hasChange_turnOn() {
+        mController.onPreferenceChange(mPreference, true);
+        mController.onRebootDialogConfirmed();
+
+        int result = Settings.Global.getInt(mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET);
+        assertThat(result).isEqualTo(VALUE_ON);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void onRebootDialogCanceled_hasChange_doNothing() {
+        mController.onPreferenceChange(mPreference, true);
+        mController.onRebootDialogCanceled();
+
+        int result = Settings.Global.getInt(mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET);
+        assertThat(result).isEqualTo(VALUE_UNSET);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void onBroadcastDisabled_currentValueOn_turnOff() {
+        Settings.Global.putInt(mContext.getContentResolver(), VALUE_KEY, VALUE_ON);
+        mController.updateState(mPreference);
+        shadowOf(Looper.getMainLooper()).idle();
+        mController.onBroadcastDisabled();
+
+        int result = Settings.Global.getInt(mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET);
+        assertThat(result).isEqualTo(VALUE_OFF);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION)
+    public void onBroadcastDisabled_currentValueUnset_doNothing() {
+        mController.updateState(mPreference);
+        mController.onBroadcastDisabled();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        int result = Settings.Global.getInt(mContext.getContentResolver(), VALUE_KEY, VALUE_UNSET);
+        assertThat(result).isEqualTo(VALUE_UNSET);
+    }
+
+    @Implements(BluetoothRebootDialog.class)
+    public static class ShadowBluetoothRebootDialogFragment {
+
+        /** Shadow implementation of BluetoothRebootDialog#show */
+        @Implementation
+        public static void show(DevelopmentSettingsDashboardFragment host) {
+            // Do nothing.
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceControllerTest.java
index ed65d5b..f9f3be7 100644
--- a/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceControllerTest.java
@@ -36,6 +36,7 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+// LINT.IfChange
 @RunWith(RobolectricTestRunner.class)
 public class CallVolumePreferenceControllerTest {
     private static final String TEST_KEY = "Test_Key";
@@ -108,3 +109,4 @@
         assertThat(mController.isPublicSlice()).isTrue();
     }
 }
+// LINT.ThenChange(CallVolumePreferenceTest.kt)
diff --git a/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceTest.kt b/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceTest.kt
new file mode 100644
index 0000000..d6bc6d0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/CallVolumePreferenceTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.notification
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.res.Resources
+import android.media.AudioManager
+import android.media.AudioManager.STREAM_BLUETOOTH_SCO
+import android.media.AudioManager.STREAM_VOICE_CALL
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+// LINT.IfChange
+@RunWith(AndroidJUnit4::class)
+class CallVolumePreferenceTest {
+    private var audioHelper = mock<AudioHelper>()
+    private var mockResources = mock<Resources>()
+
+    private var audioManager: AudioManager? = null
+
+    private var callVolumePreference = CallVolumePreference()
+    private val context = object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
+        override fun getSystemService(name: String): Any? =
+            when (name) {
+                Context.AUDIO_SERVICE -> audioManager
+                else -> super.getSystemService(name)
+            }
+
+        override fun getResources(): Resources = mockResources
+    }
+
+    @Test
+    fun isAvailable_configTrueAndNoSingleVolume_shouldReturnTrue() {
+        mockResources.stub { on { getBoolean(anyInt()) } doReturn true }
+        audioHelper = mock { on { isSingleVolume } doReturn false }
+        callVolumePreference = spy(callVolumePreference).stub {
+            onGeneric { createAudioHelper(context) } doReturn audioHelper
+        }
+
+        assertThat(callVolumePreference.isAvailable(context)).isTrue()
+    }
+
+    @Test
+    fun isAvailable_configTrueAndSingleVolume_shouldReturnFalse() {
+        mockResources.stub { on { getBoolean(anyInt()) } doReturn true }
+        audioHelper = mock { on { isSingleVolume } doReturn true }
+        callVolumePreference = spy(callVolumePreference).stub {
+            onGeneric { createAudioHelper(context) } doReturn audioHelper
+        }
+
+        assertThat(callVolumePreference.isAvailable(context)).isFalse()
+    }
+
+    @Test
+    fun isAvailable_configFalse_shouldReturnFalse() {
+        mockResources.stub { on { getBoolean(anyInt()) } doReturn false }
+
+        assertThat(callVolumePreference.isAvailable(context)).isFalse()
+    }
+
+    @Test
+    @Suppress("DEPRECATION")
+    fun getAudioStream_onBluetoothScoOn_shouldEqualToStreamBluetoothSco() {
+        audioManager = mock { on { isBluetoothScoOn } doReturn true }
+
+        assertThat(callVolumePreference.getAudioStream(context)).isEqualTo(STREAM_BLUETOOTH_SCO)
+    }
+
+    @Test
+    @Suppress("DEPRECATION")
+    fun getAudioStream_onBluetoothScoOff_shouldEqualToStreamVoiceCall() {
+        audioManager = mock { on { isBluetoothScoOn } doReturn false }
+
+        assertThat(callVolumePreference.getAudioStream(context)).isEqualTo(STREAM_VOICE_CALL)
+    }
+}
+// LINT.ThenChange(CallVolumePreferenceControllerTest.java)
diff --git a/tests/robotests/src/com/android/settings/notification/app/BundleListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/BundleListPreferenceControllerTest.java
index 8b8c77e..a8de8ef 100644
--- a/tests/robotests/src/com/android/settings/notification/app/BundleListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/BundleListPreferenceControllerTest.java
@@ -89,15 +89,6 @@
         mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
         mGroupList = new PreferenceCategory(mContext);
         mPreferenceScreen.addPreference(mGroupList);
-
-        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID)).thenReturn(
-                new NotificationChannel(PROMOTIONS_ID, PROMOTIONS_ID, 2));
-        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID)).thenReturn(
-                new NotificationChannel(NEWS_ID, NEWS_ID, 2));
-        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID)).thenReturn(
-                new NotificationChannel(SOCIAL_MEDIA_ID, SOCIAL_MEDIA_ID, 2));
-        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID)).thenReturn(
-                new NotificationChannel(RECS_ID, RECS_ID, 2));
     }
 
     @Test
@@ -132,6 +123,14 @@
 
     @Test
     public void updateState() {
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID)).thenReturn(
+                new NotificationChannel(PROMOTIONS_ID, PROMOTIONS_ID, 2));
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID)).thenReturn(
+                new NotificationChannel(NEWS_ID, NEWS_ID, 2));
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID)).thenReturn(
+                new NotificationChannel(SOCIAL_MEDIA_ID, SOCIAL_MEDIA_ID, 2));
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID)).thenReturn(
+                new NotificationChannel(RECS_ID, RECS_ID, 2));
         mController.updateState(mGroupList);
         assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);
         assertThat(mGroupList.findPreference(PROMOTIONS_ID).getTitle()).isEqualTo(PROMOTIONS_ID);
@@ -142,19 +141,38 @@
     }
 
     @Test
-    public void updateState_updateChildren() {
+    public void updateState_noBundles() {
         mController.updateState(mGroupList);
-        assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);
+        assertThat(mGroupList.getPreferenceCount()).isEqualTo(0);
+        assertThat(mGroupList.isVisible()).isFalse();
+    }
 
+    @Test
+    public void updateState_onlySomeBundlesUsed() {
         when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID)).thenReturn(
                 new NotificationChannel(PROMOTIONS_ID, PROMOTIONS_ID, 2));
+        mController.updateState(mGroupList);
+        assertThat(mGroupList.getPreferenceCount()).isEqualTo(1);
+        assertThat(mGroupList.findPreference(PROMOTIONS_ID).getTitle()).isEqualTo(PROMOTIONS_ID);
+    }
+
+    @Test
+    public void updateState_noDuplicateChannelsOnReload() {
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, PROMOTIONS_ID)).thenReturn(
+                new NotificationChannel(PROMOTIONS_ID, PROMOTIONS_ID, 2));
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, NEWS_ID)).thenReturn(
+                new NotificationChannel(NEWS_ID, NEWS_ID, 2));
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, SOCIAL_MEDIA_ID)).thenReturn(
+                new NotificationChannel(SOCIAL_MEDIA_ID, SOCIAL_MEDIA_ID, 2));
+        when(mBackend.getChannel(mAppRow.pkg, mAppRow.uid, RECS_ID)).thenReturn(
+                new NotificationChannel(RECS_ID, RECS_ID, 2));
 
         mController.updateState(mGroupList);
         assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);
+        mController.updateState(mGroupList);
+        assertThat(mGroupList.getPreferenceCount()).isEqualTo(4);
 
         assertThat(((PrimarySwitchPreference) mGroupList.findPreference(NEWS_ID)).isChecked())
                 .isEqualTo(false);
-        assertThat(((PrimarySwitchPreference) mGroupList.findPreference(NEWS_ID)).isChecked())
-                .isEqualTo(false);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java
index b7af71b..d916dcf 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceControllerTest.java
@@ -44,6 +44,8 @@
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.SystemZenRules;
 import android.service.notification.ZenModeConfig;
+import android.text.Spanned;
+import android.text.style.TtsSpan;
 import android.widget.TextView;
 
 import androidx.preference.PreferenceManager;
@@ -293,7 +295,14 @@
 
         assertThat(mPreference.isVisible()).isTrue();
         assertThat(mPreference.getTitle()).isEqualTo("1:00 AM - 3:00 PM");
-        assertThat(mPreference.getSummary()).isEqualTo("Mon - Tue, Thu");
+        Spanned summary = (Spanned) mPreference.getSummary();
+        assertThat(summary.toString()).isEqualTo("Mon - Tue, Thu");
+        TtsSpan[] ttsSpans = summary.getSpans(0, summary.length(), TtsSpan.class);
+        assertThat(ttsSpans).hasLength(1);
+        assertThat(ttsSpans[0].getType()).isEqualTo(TtsSpan.TYPE_TEXT);
+        assertThat(ttsSpans[0].getArgs().getString(TtsSpan.ARG_TEXT)).isEqualTo(
+                "Monday to Tuesday, Thursday");
+
         // Destination as written into the intent by SubSettingLauncher
         assertThat(
                 mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index be43f8e..07df3c8 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,11 +26,14 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "flag-junit",
+        "kotlin-test",
+        "mockito-kotlin2",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
         "platform-test-rules",
         "truth",
         "kotlinx_coroutines_test",
+        "SettingsLibPreference-testutils",
         "Settings-testutils2",
         "servicestests-utils",
         // Don't add SettingsLib libraries here - you can use them directly as they are in the
diff --git a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
index 7e942d9..9a09bf1 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
@@ -93,7 +93,7 @@
         doReturn(mUserManager).when(mContext).getSystemService(Context.USER_SERVICE);
         when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager);
         when(mBiometricManager.canAuthenticate(mContext.getUserId(),
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                BiometricManager.Authenticators.IDENTITY_CHECK))
                 .thenReturn(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
 
         mFactory = FakeFeatureFactory.setupForTest();
@@ -213,7 +213,7 @@
     public void onActivityResult_confirmPasswordRequestCompleted_launchBiometricPrompt() {
         when(mUserManager.isAdminUser()).thenReturn(true);
         when(mBiometricManager.canAuthenticate(mContext.getUserId(),
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
+                BiometricManager.Authenticators.IDENTITY_CHECK))
                 .thenReturn(BiometricManager.BIOMETRIC_SUCCESS);
 
         final boolean activityResultHandled = mController.onActivityResult(
@@ -233,8 +233,8 @@
     public void onActivityResult_confirmPasswordRequestCompleted_mandatoryBiometricsError() {
         when(mUserManager.isAdminUser()).thenReturn(true);
         when(mBiometricManager.canAuthenticate(mContext.getUserId(),
-                BiometricManager.Authenticators.MANDATORY_BIOMETRICS))
-                .thenReturn(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
+                BiometricManager.Authenticators.IDENTITY_CHECK))
+                .thenReturn(BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE);
 
         final boolean activityResultHandled = mController.onActivityResult(
                 BuildNumberPreferenceController.REQUEST_CONFIRM_PASSWORD_FOR_DEV_PREF,
diff --git a/tests/unit/src/com/android/settings/language/LanguageSettingScreenTest.kt b/tests/unit/src/com/android/settings/language/LanguageSettingScreenTest.kt
new file mode 100644
index 0000000..7b519a2
--- /dev/null
+++ b/tests/unit/src/com/android/settings/language/LanguageSettingScreenTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.language
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import com.android.settings.Settings.LanguageSettingsActivity
+import com.android.settings.flags.Flags
+import com.android.settingslib.preference.CatalystScreenTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+class LanguageSettingScreenTest: CatalystScreenTestCase() {
+    override val preferenceScreenCreator = LanguageSettingScreen()
+
+    override val flagName: String
+        get() = Flags.FLAG_CATALYST_LANGUAGE_SETTING
+
+    @Test
+    fun key() {
+        assertThat(preferenceScreenCreator.key).isEqualTo(LanguageSettingScreen.KEY)
+    }
+
+    override fun migration() {}
+}