Merge "[Audiosharing] Fix tests for hysteresis 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..4831543 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5995,6 +5995,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 +7421,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/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..5d58fc1 100644
--- a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
@@ -45,6 +45,7 @@
 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.FingerprintSensorInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
@@ -113,6 +114,9 @@
   fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
     CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
 
+  fun createFingerprintEnrollStageCountInteractor(): FingerprintEnrollStageCountInteractor =
+    FingerprintEnrollStageCountInteractor(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..23ad8ef 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,8 @@
   /** Indicates if a user can enroll another fingerprint */
   val canEnrollUser: Flow<Boolean>
 
+  val enrollStageCount: Int
+
   /**
    * Indicates if we should use the default settings for maximum enrollments or the sensor props
    * from the fingerprint sensor
@@ -115,4 +117,7 @@
       ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
       ?.toList()
   }
+
+  override val enrollStageCount: Int
+    get() = fingerprintManager.enrollStageCount
 }
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/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/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/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/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/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() {}
+}