Merge "[Audiosharing] Add loading state dialog" into main
diff --git a/Android.bp b/Android.bp
index 25fc8f56..8b903ba 100644
--- a/Android.bp
+++ b/Android.bp
@@ -111,7 +111,10 @@
         "keyboard_flags_lib",
     ],
 
-    plugins: ["androidx.room_room-compiler-plugin"],
+    plugins: [
+        "SettingsLibMetadata-processor",
+        "androidx.room_room-compiler-plugin",
+    ],
 
     errorprone: {
         extra_check_modules: ["//external/nullaway:nullaway_plugin"],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e204dd9..66c3beb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1292,7 +1292,7 @@
 
         <activity
             android:name="Settings$ModesSettingsActivity"
-            android:label="@string/zen_mode_settings_title"
+            android:label="@string/zen_modes_list_title"
             android:icon="@drawable/ic_homepage_notification"
             android:exported="true">
             <intent-filter android:priority="1"
diff --git a/res/layout/sfps_enroll_enrolling.xml b/res/layout/sfps_enroll_enrolling.xml
index e0f9d1f..bd6e69a 100644
--- a/res/layout/sfps_enroll_enrolling.xml
+++ b/res/layout/sfps_enroll_enrolling.xml
@@ -45,7 +45,7 @@
                 android:id="@+id/illustration_lottie"
                 android:layout_width="@dimen/fingerprint_progress_bar_max_size"
                 android:layout_height="@dimen/fingerprint_progress_bar_max_size"
-                android:layout_marginRight="@dimen/sfps_lottie_translate_x"
+                android:layout_marginEnd="@dimen/sfps_lottie_translate_x"
                 android:layout_marginBottom="@dimen/sfps_lottie_translate_y"
                 android:scaleType="centerInside"
                 android:visibility="gone"
@@ -66,7 +66,7 @@
                     android:id="@+id/fingerprint_progress_bar"
                     android:layout_width="match_parent"
                     android:layout_height="match_parent"
-                    android:layout_marginLeft="@dimen/sfps_progress_bar_translate_x"
+                    android:layout_marginStart="@dimen/sfps_progress_bar_translate_x"
                     android:layout_marginTop="@dimen/sfps_progress_bar_translate_y"
                     android:layout_gravity="center"
                     android:minHeight="@dimen/fingerprint_progress_bar_min_size"
diff --git a/res/values-ldrtl/dimens.xml b/res/values-ldrtl/dimens.xml
index cbe7eb5..f11b2f4 100755
--- a/res/values-ldrtl/dimens.xml
+++ b/res/values-ldrtl/dimens.xml
@@ -23,4 +23,8 @@
     <dimen name="rotation_90_enroll_padding_start">20dp</dimen>
     <dimen name="rotation_90_enroll_padding_end">0dp</dimen>
     <dimen name="rotation_90_enroll_margin_end">20dp</dimen>
+
+    <!-- SFPS Fingerprint -->
+    <dimen name="fingerprint_progress_bar_max_size">242dp</dimen>
+    <dimen name="sfps_lottie_translate_x">10dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c63e5f4..c1207cf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5190,6 +5190,8 @@
     <string name="accessibility_button_low_label">Transparent</string>
     <!-- Label on the right side of transparency adjustment slider [CHAR LIMIT=30] -->
     <string name="accessibility_button_high_label">Non-transparent</string>
+    <!-- Summary for settings that are disabled in the current button mode [CHAR LIMIT=30] -->
+    <string name="accessibility_button_disabled_button_mode_summary">Unavailable while using navigation bar button mode.</string>
     <!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
     <string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string>
     <!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java
index d208fdf..7e008e4 100644
--- a/src/com/android/settings/SettingsApplication.java
+++ b/src/com/android/settings/SettingsApplication.java
@@ -16,6 +16,8 @@
 
 package com.android.settings;
 
+import static com.android.settingslib.flags.Flags.settingsCatalyst;
+
 import android.app.Application;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -42,13 +44,20 @@
 import com.android.settings.spa.SettingsSpaEnvironment;
 import com.android.settingslib.applications.AppIconCacheManager;
 import com.android.settingslib.datastore.BackupRestoreStorageManager;
+import com.android.settingslib.metadata.PreferenceScreenMetadata;
+import com.android.settingslib.metadata.PreferenceScreenRegistry;
+import com.android.settingslib.metadata.ProvidePreferenceScreenOptions;
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory;
 
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
 import java.lang.ref.WeakReference;
+import java.util.List;
 
 /** Settings application which sets up activity embedding rules for the large screen device. */
+@ProvidePreferenceScreenOptions(
+        codegenCollector = "com.android.settings/PreferenceScreenCollector/get"
+)
 public class SettingsApplication extends Application {
 
     private WeakReference<SettingsHomepageActivity> mHomeActivity = new WeakReference<>(null);
@@ -64,6 +73,11 @@
     public void onCreate() {
         super.onCreate();
 
+        if (settingsCatalyst()) {
+            PreferenceScreenRegistry.INSTANCE.setPreferenceScreensSupplier(
+                    this::getPreferenceScreens);
+        }
+
         BackupRestoreStorageManager.getInstance(this)
                 .add(
                         new BatterySettingsStorage(this),
@@ -90,6 +104,13 @@
         registerActivityLifecycleCallbacks(new DeveloperOptionsActivityLifecycle());
     }
 
+    /** Returns the screens using metadata. */
+    protected List<PreferenceScreenMetadata> getPreferenceScreens() {
+        // PreferenceScreenCollector is generated by annotation processor from classes annotated
+        // with @ProvidePreferenceScreen
+        return PreferenceScreenCollector.get(this);
+    }
+
     @Override
     public void onTerminate() {
         BackupRestoreStorageManager.getInstance(this).removeAll();
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonFragment.java b/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
index 2df11a9..60e4bb2 100644
--- a/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityButtonFragment.java
@@ -33,8 +33,15 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        final int titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
-                ? R.string.accessibility_button_gesture_title : R.string.accessibility_button_title;
+
+        final int titleResource;
+        if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            titleResource = R.string.accessibility_button_title;
+        } else {
+            titleResource = AccessibilityUtil.isGestureNavigateEnabled(getPrefContext())
+                    ? R.string.accessibility_button_gesture_title
+                    : R.string.accessibility_button_title;
+        }
         getActivity().setTitle(titleResource);
     }
 
diff --git a/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java b/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java
index ded8aca..6db28a6 100644
--- a/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java
+++ b/src/com/android/settings/accessibility/AccessibilityButtonGesturePreferenceController.java
@@ -50,8 +50,12 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return AccessibilityUtil.isGestureNavigateEnabled(mContext)
-                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+        if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+            return CONDITIONALLY_UNAVAILABLE;
+        } else {
+            return AccessibilityUtil.isGestureNavigateEnabled(mContext)
+                    ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java b/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java
index 2895e75..85b48e9 100644
--- a/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java
+++ b/src/com/android/settings/accessibility/CaptioningMoreOptionsFragment.java
@@ -17,6 +17,8 @@
 package com.android.settings.accessibility;
 
 import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.provider.Settings;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -50,5 +52,16 @@
     }
 
     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider(R.xml.captioning_more_options);
+            new BaseSearchIndexProvider(R.xml.captioning_more_options) {
+                @Override
+                protected boolean isPageSearchEnabled(Context context) {
+                    if (!Flags.fixA11ySettingsSearch()) {
+                        return super.isPageSearchEnabled(context);
+                    }
+                    // CaptioningMoreOptions is only searchable if captions are enabled, so that we
+                    // don't show search results for settings that will cause no change to the user.
+                    return Settings.Secure.getInt(context.getContentResolver(),
+                            Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, 0) == 1;
+                }
+            };
 }
diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
index 3d36aa4..70882a4 100644
--- a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
+++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java
@@ -28,6 +28,7 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.TwoStatePreference;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -65,6 +66,18 @@
     }
 
     @Override
+    public CharSequence getSummary() {
+        if (mPreference != null) {
+            return mPreference.isEnabled()
+                    ? "%s"
+                    : mContext.getString(
+                            R.string.accessibility_button_disabled_button_mode_summary);
+        } else {
+            return "%s";
+        }
+    }
+
+    @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
 
@@ -101,6 +114,7 @@
 
     private void updateAvailabilityStatus() {
         mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
+        refreshSummary(mPreference);
     }
 
     private int getFloatingMenuFadeValue() {
diff --git a/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
index 7d4f495..2f036da 100644
--- a/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
+++ b/src/com/android/settings/accessibility/FloatingMenuSizePreferenceController.java
@@ -29,6 +29,7 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -80,6 +81,18 @@
     }
 
     @Override
+    public CharSequence getSummary() {
+        if (mPreference != null) {
+            return mPreference.isEnabled()
+                    ? "%s"
+                    : mContext.getString(
+                            R.string.accessibility_button_disabled_button_mode_summary);
+        } else {
+            return "%s";
+        }
+    }
+
+    @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
 
@@ -119,6 +132,7 @@
 
     private void updateAvailabilityStatus() {
         mPreference.setEnabled(AccessibilityUtil.isFloatingMenuEnabled(mContext));
+        refreshSummary(mPreference);
     }
 
     @Size
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 4cb540c..e42c654 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -26,6 +26,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
@@ -52,7 +53,9 @@
 import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -81,7 +84,9 @@
     private static final String LE_AUDIO_TOGGLE_VISIBLE_PROPERTY =
             "persist.bluetooth.leaudio.toggle_visible";
 
-    private final AtomicReference<Set<String>> mInvisiblePreferenceKey = new AtomicReference<>();
+    private Set<String> mInvisibleProfiles = Collections.emptySet();
+    private final AtomicReference<Set<String>> mAdditionalInvisibleProfiles =
+            new AtomicReference<>();
 
     private LocalBluetoothManager mManager;
     private LocalBluetoothProfileManager mProfileManager;
@@ -95,13 +100,21 @@
     @VisibleForTesting
     PreferenceCategory mProfilesContainer;
 
-    public BluetoothDetailsProfilesController(Context context, PreferenceFragmentCompat fragment,
-            LocalBluetoothManager manager, CachedBluetoothDevice device, Lifecycle lifecycle) {
+    public BluetoothDetailsProfilesController(
+            Context context,
+            PreferenceFragmentCompat fragment,
+            LocalBluetoothManager manager,
+            CachedBluetoothDevice device,
+            Lifecycle lifecycle,
+            @Nullable List<String> invisibleProfiles) {
         super(context, fragment, device, lifecycle);
         mManager = manager;
         mProfileManager = mManager.getProfileManager();
         mCachedDevice = device;
         mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice);
+        if (invisibleProfiles != null) {
+            mInvisibleProfiles = Set.copyOf(invisibleProfiles);
+        }
     }
 
     @Override
@@ -563,7 +576,7 @@
     protected void refresh() {
         ThreadUtils.postOnBackgroundThread(
                 () -> {
-                    mInvisiblePreferenceKey.set(
+                    mAdditionalInvisibleProfiles.set(
                             FeatureFactory.getFeatureFactory()
                                     .getBluetoothFeatureProvider()
                                     .getInvisibleProfilePreferenceKeys(
@@ -604,12 +617,15 @@
             mProfilesContainer.addPreference(preference);
         }
 
-        Set<String> invisibleKeys = mInvisiblePreferenceKey.get();
-        if (invisibleKeys != null) {
-            for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) {
-                Preference pref = mProfilesContainer.getPreference(i);
-                pref.setVisible(pref.isVisible() && !invisibleKeys.contains(pref.getKey()));
-            }
+        Set<String> additionalInvisibleProfiles = mAdditionalInvisibleProfiles.get();
+        HashSet<String> combinedInvisibleProfiles = new HashSet<>(mInvisibleProfiles);
+        if (additionalInvisibleProfiles != null) {
+            combinedInvisibleProfiles.addAll(additionalInvisibleProfiles);
+        }
+        Log.i(TAG, "Invisible profiles: " + combinedInvisibleProfiles);
+        for (int i = 0; i < mProfilesContainer.getPreferenceCount(); ++i) {
+            Preference pref = mProfilesContainer.getPreference(i);
+            pref.setVisible(pref.isVisible() && !combinedInvisibleProfiles.contains(pref.getKey()));
         }
     }
 
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
index 844a7c0..0e51d17 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java
@@ -419,12 +419,16 @@
 
     @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        List<String> invisibleProfiles = List.of();
         if (Flags.enableBluetoothDeviceDetailsPolish()) {
             mFormatter =
                     FeatureFactory.getFeatureFactory()
                             .getBluetoothFeatureProvider()
                             .getDeviceDetailsFragmentFormatter(
                                     requireContext(), this, mBluetoothAdapter, mCachedDevice);
+            invisibleProfiles =
+                    mFormatter.getInvisibleBluetoothProfiles(
+                            FragmentTypeModel.DeviceDetailsMainFragment.INSTANCE);
         }
         ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
 
@@ -444,7 +448,7 @@
             controllers.add(new BluetoothDetailsSpatialAudioController(context, this, mCachedDevice,
                     lifecycle));
             controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
-                    mCachedDevice, lifecycle));
+                    mCachedDevice, lifecycle, invisibleProfiles));
             controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
                     lifecycle));
             controllers.add(new StylusDevicesController(context, mInputDevice, mCachedDevice,
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
index 8f0bf3e..f2a569d 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
@@ -43,6 +43,7 @@
 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 import com.android.settings.spa.preference.ComposePreference
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -69,6 +70,9 @@
     fun getVisiblePreferenceKeys(fragmentType: FragmentTypeModel): List<String>?
 
     /** Updates device details fragment layout. */
+    fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>?
+
+    /** Updates device details fragment layout. */
     fun updateLayout(fragmentType: FragmentTypeModel)
 
     /** Gets the menu items of the fragment. */
@@ -108,13 +112,22 @@
             viewModel
                 .getItems(fragmentType)
                 ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
-                ?.mapNotNull { it.preferenceKey }
+                ?.map { it.preferenceKey }
+        }
+
+    override fun getInvisibleBluetoothProfiles(fragmentType: FragmentTypeModel): List<String>? =
+        runBlocking {
+            viewModel
+                .getItems(fragmentType)
+                ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem>()
+                ?.first()?.invisibleProfiles
         }
 
     /** Updates bluetooth device details fragment layout. */
     override fun updateLayout(fragmentType: FragmentTypeModel) = runBlocking {
         val items = viewModel.getItems(fragmentType) ?: return@runBlocking
         val layout = viewModel.getLayout(fragmentType) ?: return@runBlocking
+
         val prefKeyToSettingId =
             items
                 .filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
index f69f023..c0fbd4f 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsMoreSettingsFragment.kt
@@ -22,7 +22,6 @@
 import android.graphics.PorterDuff
 import android.os.Bundle
 import android.view.Menu
-import android.view.MenuInflater
 import android.view.MenuItem
 import androidx.lifecycle.lifecycleScope
 import com.android.settings.R
@@ -63,8 +62,10 @@
                 item.icon?.setColorFilter(
                     resources.getColor(
                         com.android.settingslib.widget.theme.R.color
-                            .settingslib_materialColorOnSurface),
-                    PorterDuff.Mode.SRC_ATOP)
+                            .settingslib_materialColorOnSurface
+                    ),
+                    PorterDuff.Mode.SRC_ATOP,
+                )
                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
             }
         }
@@ -116,14 +117,27 @@
                 }
         formatter =
             featureFactory.bluetoothFeatureProvider.getDeviceDetailsFragmentFormatter(
-                requireContext(), this, bluetoothManager.adapter, cachedDevice)
+                requireContext(),
+                this,
+                bluetoothManager.adapter,
+                cachedDevice,
+            )
         helpItem =
             formatter
                 .getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment)
                 .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), initialValue = null)
         return listOf(
             BluetoothDetailsProfilesController(
-                context, this, localBluetoothManager, cachedDevice, settingsLifecycle))
+                context,
+                this,
+                localBluetoothManager,
+                cachedDevice,
+                settingsLifecycle,
+                formatter.getInvisibleBluetoothProfiles(
+                    FragmentTypeModel.DeviceDetailsMoreSettingsFragment
+                ),
+            )
+        )
     }
 
     override fun getLogTag(): String = TAG
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
new file mode 100644
index 0000000..5de615e
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
+public class AudioSharingIncompatibleDialogFragment extends InstrumentedDialogFragment {
+    private static final String TAG = "AudioSharingIncompatDlg";
+
+    private static final String BUNDLE_KEY_DEVICE_NAME = "bundle_key_device_name";
+
+    // The host creates an instance of this dialog fragment must implement this interface to receive
+    // event callbacks.
+    public interface DialogEventListener {
+        /**
+         * Called when the dialog is dismissed.
+         */
+        void onDialogDismissed();
+    }
+
+    @Nullable
+    private static DialogEventListener sListener;
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO: add metrics
+        return 0;
+    }
+
+    /**
+     * Display the {@link AudioSharingIncompatibleDialogFragment} dialog.
+     *
+     * @param host The Fragment this dialog will be hosted.
+     */
+    public static void show(@Nullable Fragment host, @NonNull CachedBluetoothDevice cachedDevice,
+            @NonNull DialogEventListener listener) {
+        if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
+        final FragmentManager manager;
+        try {
+            manager = host.getChildFragmentManager();
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "Fail to show dialog: " + e.getMessage());
+            return;
+        }
+        sListener = listener;
+        AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
+        if (dialog != null) {
+            Log.d(TAG, "Dialog is showing, return.");
+            return;
+        }
+        Log.d(TAG, "Show up the incompatible device dialog.");
+        final Bundle bundle = new Bundle();
+        bundle.putString(BUNDLE_KEY_DEVICE_NAME, cachedDevice.getName());
+        AudioSharingIncompatibleDialogFragment dialogFrag =
+                new AudioSharingIncompatibleDialogFragment();
+        dialogFrag.setArguments(bundle);
+        dialogFrag.show(manager, TAG);
+    }
+
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        Bundle arguments = requireArguments();
+        String deviceName = arguments.getString(BUNDLE_KEY_DEVICE_NAME);
+        // TODO: move strings to res once they are finalized
+        AlertDialog dialog =
+                AudioSharingDialogFactory.newBuilder(getActivity())
+                        .setTitle("Can't share audio with " + deviceName)
+                        .setTitleIcon(com.android.settings.R.drawable.ic_warning_24dp)
+                        .setIsCustomBodyEnabled(true)
+                        .setCustomMessage(
+                                "Audio sharing only works with headphones that support LE Audio.")
+                        .setPositiveButton(com.android.settings.R.string.okay, (d, w) -> {})
+                        .build();
+        dialog.setCanceledOnTouchOutside(true);
+        return dialog;
+    }
+
+    @Override
+    public void onDismiss(@NonNull DialogInterface dialog) {
+        super.onDismiss(dialog);
+        if (sListener != null) {
+            sListener.onDialogDismissed();
+        }
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragment.java
new file mode 100644
index 0000000..822e053
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragment.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+
+public class AudioSharingRetryDialogFragment extends InstrumentedDialogFragment {
+    private static final String TAG = "AudioSharingRetryDialog";
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO: add metrics
+        return 0;
+    }
+
+    /**
+     * Display the {@link AudioSharingRetryDialogFragment} dialog.
+     *
+     * @param host The Fragment this dialog will be hosted.
+     */
+    public static void show(@Nullable Fragment host) {
+        if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
+        final FragmentManager manager;
+        try {
+            manager = host.getChildFragmentManager();
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "Fail to show dialog: " + e.getMessage());
+            return;
+        }
+        AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
+        if (dialog != null) {
+            Log.d(TAG, "Dialog is showing, return.");
+            return;
+        }
+        Log.d(TAG, "Show up the retry dialog.");
+        AudioSharingRetryDialogFragment dialogFrag = new AudioSharingRetryDialogFragment();
+        dialogFrag.show(manager, TAG);
+    }
+
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        // TODO: put strings to res till they are finalized
+        AlertDialog dialog =
+                AudioSharingDialogFactory.newBuilder(getActivity())
+                        .setTitle("Couldn't share audio")
+                        .setTitleIcon(com.android.settings.R.drawable.ic_warning_24dp)
+                        .setIsCustomBodyEnabled(true)
+                        .setCustomMessage("Something went wrong. Please try again.")
+                        .setPositiveButton(com.android.settings.R.string.okay, (d, w) -> {
+                        })
+                        .build();
+        dialog.setCanceledOnTouchOutside(true);
+        return dialog;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 6501084..2040694 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -25,7 +25,6 @@
 import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -377,9 +376,9 @@
             // FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in
             // prod. We can turn off the flag for debug purpose.
             if (FeatureFlagUtils.isEnabled(
-                            mContext,
-                            FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)
-                    && mAssistant.getAllConnectedDevices().isEmpty()) {
+                    mContext,
+                    FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)
+                    && hasEmptyConnectedSink()) {
                 // Pop up dialog to ask users to connect at least one lea buds before audio sharing.
                 AudioSharingUtils.postOnMainThread(
                         mContext,
@@ -435,8 +434,12 @@
     }
 
     @Override
-    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
-        if (activeDevice != null && bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+    public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
+            int bluetoothProfile) {
+        if (activeDevice != null) {
+            Log.d(TAG, "onActiveDeviceChanged: device = "
+                    + activeDevice.getDevice().getAnonymizedAddress()
+                    + ", profile = " + bluetoothProfile);
             updateSwitch();
         }
     }
@@ -536,13 +539,19 @@
                             boolean isBroadcasting = BluetoothUtils.isBroadcasting(mBtManager);
                             boolean hasActiveDevice =
                                     AudioSharingUtils.hasActiveConnectedLeadDevice(mBtManager);
+                            boolean hasEmptyConnectedDevice = hasEmptyConnectedSink();
                             boolean isStateReady =
                                     isBluetoothOn()
                                             && AudioSharingUtils.isAudioSharingProfileReady(
-                                                    mProfileManager)
+                                            mProfileManager)
+                                            && (isBroadcasting
+                                            // Always enable toggle when no connected sink. We have
+                                            // dialog to guide users to connect compatible devices
+                                            // for audio sharing.
+                                            || hasEmptyConnectedDevice
                                             // Disable toggle till device gets active after
                                             // broadcast ends.
-                                            && (isBroadcasting || hasActiveDevice);
+                                            || hasActiveDevice);
                             AudioSharingUtils.postOnMainThread(
                                     mContext,
                                     () -> {
@@ -566,6 +575,10 @@
         return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
     }
 
+    private boolean hasEmptyConnectedSink() {
+        return mAssistant != null && mAssistant.getAllConnectedDevices().isEmpty();
+    }
+
     private void handleOnBroadcastReady() {
         Pair<Integer, Object>[] eventData =
                 AudioSharingUtils.buildAudioSharingDialogEventData(
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index 43256bc..a662809 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -215,15 +215,12 @@
             @Nullable LocalBluetoothManager localBtManager) {
         CachedBluetoothDeviceManager deviceManager =
                 localBtManager == null ? null : localBtManager.getCachedDeviceManager();
-        Map<Integer, List<BluetoothDevice>> groupedConnectedDevices =
-                fetchConnectedDevicesByGroupId(localBtManager);
-        for (List<BluetoothDevice> devices : groupedConnectedDevices.values()) {
-            CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices);
-            if (isActiveLeAudioDevice(leadDevice)) {
-                return true;
-            }
+        if (deviceManager == null) {
+            Log.d(TAG, "hasActiveConnectedLeadDevice return false due to null device manager.");
+            return false;
         }
-        return false;
+        return deviceManager.getCachedDevicesCopy().stream().anyMatch(
+                BluetoothUtils::isActiveMediaDevice);
     }
 
     /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index 0808da1..09dd1dd 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -15,6 +15,8 @@
  */
 package com.android.settings.dashboard;
 
+import static com.android.settingslib.flags.Flags.settingsCatalyst;
+
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.ContentResolver;
@@ -53,6 +55,7 @@
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.metadata.PreferenceScreenRegistry;
 import com.android.settingslib.search.Indexable;
 
 import java.util.ArrayList;
@@ -97,30 +100,35 @@
                 R.array.config_suppress_injected_tile_keys));
         mDashboardFeatureProvider =
                 FeatureFactory.getFeatureFactory().getDashboardFeatureProvider();
-        // Load preference controllers from code
-        final List<AbstractPreferenceController> controllersFromCode =
-                createPreferenceControllers(context);
-        // Load preference controllers from xml definition
-        final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
-                .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
-        // Filter xml-based controllers in case a similar controller is created from code already.
-        final List<BasePreferenceController> uniqueControllerFromXml =
-                PreferenceControllerListHelper.filterControllers(
-                        controllersFromXml, controllersFromCode);
 
-        // Add unique controllers to list.
-        if (controllersFromCode != null) {
-            mControllers.addAll(controllersFromCode);
-        }
-        mControllers.addAll(uniqueControllerFromXml);
+        if (!usePreferenceScreenMetadata() || PreferenceScreenRegistry.INSTANCE.get(
+                getPreferenceScreenBindingKey(context)) == null) {
+            // Load preference controllers from code
+            final List<AbstractPreferenceController> controllersFromCode =
+                    createPreferenceControllers(context);
+            // Load preference controllers from xml definition
+            final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
+                    .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
+            // Filter xml-based controllers in case a similar controller is created from code
+            // already.
+            final List<BasePreferenceController> uniqueControllerFromXml =
+                    PreferenceControllerListHelper.filterControllers(
+                            controllersFromXml, controllersFromCode);
 
-        // And wire up with lifecycle.
-        final Lifecycle lifecycle = getSettingsLifecycle();
-        uniqueControllerFromXml.forEach(controller -> {
-            if (controller instanceof LifecycleObserver) {
-                lifecycle.addObserver((LifecycleObserver) controller);
+            // Add unique controllers to list.
+            if (controllersFromCode != null) {
+                mControllers.addAll(controllersFromCode);
             }
-        });
+            mControllers.addAll(uniqueControllerFromXml);
+
+            // And wire up with lifecycle.
+            final Lifecycle lifecycle = getSettingsLifecycle();
+            uniqueControllerFromXml.forEach(controller -> {
+                if (controller instanceof LifecycleObserver) {
+                    lifecycle.addObserver((LifecycleObserver) controller);
+                }
+            });
+        }
 
         // Set metrics category for BasePreferenceController.
         final int metricCategory = getMetricsCategory();
@@ -273,6 +281,11 @@
     }
 
     @Override
+    protected final int getPreferenceScreenResId(@NonNull Context context) {
+        return getPreferenceScreenResId();
+    }
+
+    @Override
     protected abstract int getPreferenceScreenResId();
 
     @Override
@@ -364,12 +377,32 @@
         if (resId <= 0) {
             return;
         }
-        addPreferencesFromResource(resId);
-        final PreferenceScreen screen = getPreferenceScreen();
+        PreferenceScreen screen;
+        if (usePreferenceScreenMetadata()) {
+            screen = createPreferenceScreen();
+            setPreferenceScreen(screen);
+            requireActivity().setTitle(screen.getTitle());
+        } else {
+            addPreferencesFromResource(resId);
+            screen = getPreferenceScreen();
+        }
         screen.setOnExpandButtonClickListener(this);
         displayResourceTilesToScreen(screen);
     }
 
+    @Override
+    protected final boolean usePreferenceScreenMetadata() {
+        return settingsCatalyst() && enableCatalyst();
+    }
+
+    /**
+     * Returns if settings catalyst should be enabled (e.g. check trunk stable flag) on current
+     * screen.
+     */
+    protected boolean enableCatalyst() {
+        return false;
+    }
+
     /**
      * Perform {@link AbstractPreferenceController#displayPreference(PreferenceScreen)}
      * on all {@link AbstractPreferenceController}s.
diff --git a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt
index 8852540..99683a8 100644
--- a/src/com/android/settings/network/telephony/CarrierConfigRepository.kt
+++ b/src/com/android/settings/network/telephony/CarrierConfigRepository.kt
@@ -17,6 +17,7 @@
 package com.android.settings.network.telephony
 
 import android.content.Context
+import android.os.Build
 import android.os.PersistableBundle
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionManager
@@ -35,7 +36,8 @@
     private enum class KeyType {
         BOOLEAN,
         INT,
-        STRING
+        INT_ARRAY,
+        STRING,
     }
 
     interface CarrierConfigAccessor {
@@ -43,17 +45,20 @@
 
         fun getInt(key: String): Int
 
+        fun getIntArray(key: String): IntArray?
+
         fun getString(key: String): String?
     }
 
     private class Accessor(private val cache: ConfigCache) : CarrierConfigAccessor {
         private val keysToRetrieve = mutableMapOf<String, KeyType>()
+        private var isKeysToRetrieveFrozen = false
 
         override fun getBoolean(key: String): Boolean {
             checkBooleanKey(key)
             val value = cache[key]
             return if (value == null) {
-                keysToRetrieve += key to KeyType.BOOLEAN
+                addKeyToRetrieve(key, KeyType.BOOLEAN)
                 DefaultConfig.getBoolean(key)
             } else {
                 check(value is BooleanConfigValue) { "Boolean value type wrong" }
@@ -65,7 +70,7 @@
             check(key.endsWith("_int")) { "Int key should ends with _int" }
             val value = cache[key]
             return if (value == null) {
-                keysToRetrieve += key to KeyType.INT
+                addKeyToRetrieve(key, KeyType.INT)
                 DefaultConfig.getInt(key)
             } else {
                 check(value is IntConfigValue) { "Int value type wrong" }
@@ -73,11 +78,23 @@
             }
         }
 
+        override fun getIntArray(key: String): IntArray? {
+            checkIntArrayKey(key)
+            val value = cache[key]
+            return if (value == null) {
+                addKeyToRetrieve(key, KeyType.INT_ARRAY)
+                DefaultConfig.getIntArray(key)
+            } else {
+                check(value is IntArrayConfigValue) { "Int array value type wrong" }
+                value.value
+            }
+        }
+
         override fun getString(key: String): String? {
             check(key.endsWith("_string")) { "String key should ends with _string" }
             val value = cache[key]
             return if (value == null) {
-                keysToRetrieve += key to KeyType.STRING
+                addKeyToRetrieve(key, KeyType.STRING)
                 DefaultConfig.getString(key)
             } else {
                 check(value is StringConfigValue) { "String value type wrong" }
@@ -85,20 +102,35 @@
             }
         }
 
-        fun getKeysToRetrieve(): Map<String, KeyType> = keysToRetrieve
+        private fun addKeyToRetrieve(key: String, type: KeyType) {
+            if (keysToRetrieve.put(key, type) == null && Build.IS_DEBUGGABLE) {
+                check(!isKeysToRetrieveFrozen) { "implement error for key $key" }
+            }
+        }
+
+        /**
+         * Gets the keys to retrieve.
+         *
+         * After this function is called, the keys to retrieve is frozen.
+         */
+        fun getAndFrozeKeysToRetrieve(): Map<String, KeyType> {
+            isKeysToRetrieveFrozen = true
+            return keysToRetrieve
+        }
     }
 
     /**
      * Gets the configuration values for the given [subId].
      *
      * Configuration values could be accessed in [block]. Note: [block] could be called multiple
-     * times, so it should be pure function without side effort.
+     * times, so it should be pure function without side effort. Please also make sure every key is
+     * retrieved every time, for example, we need avoid expression shortcut.
      */
     fun <T> transformConfig(subId: Int, block: CarrierConfigAccessor.() -> T): T {
         val perSubCache = getPerSubCache(subId)
         val accessor = Accessor(perSubCache)
         val result = accessor.block()
-        val keysToRetrieve = accessor.getKeysToRetrieve()
+        val keysToRetrieve = accessor.getAndFrozeKeysToRetrieve()
         // If all keys found in the first pass, no need to collect again
         if (keysToRetrieve.isEmpty()) return result
 
@@ -113,6 +145,10 @@
     /** Gets the configuration int for the given [subId] and [key]. */
     fun getInt(subId: Int, key: String): Int = transformConfig(subId) { getInt(key) }
 
+    /** Gets the configuration int array for the given [subId] and [key]. */
+    fun getIntArray(subId: Int, key: String): IntArray? =
+        transformConfig(subId) { getIntArray(key) }
+
     /** Gets the configuration string for the given [subId] and [key]. */
     fun getString(subId: Int, key: String): String? = transformConfig(subId) { getString(key) }
 
@@ -122,6 +158,7 @@
             when (type) {
                 KeyType.BOOLEAN -> this[key] = BooleanConfigValue(config.getBoolean(key))
                 KeyType.INT -> this[key] = IntConfigValue(config.getInt(key))
+                KeyType.INT_ARRAY -> this[key] = IntArrayConfigValue(config.getIntArray(key))
                 KeyType.STRING -> this[key] = StringConfigValue(config.getString(key))
             }
         }
@@ -195,6 +232,10 @@
             }
         }
 
+        private fun checkIntArrayKey(key: String) {
+            check(key.endsWith("_int_array")) { "Int array key should ends with _int_array" }
+        }
+
         @VisibleForTesting
         fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
             checkBooleanKey(key)
@@ -208,6 +249,12 @@
         }
 
         @VisibleForTesting
+        fun setIntArrayForTest(subId: Int, key: String, value: IntArray?) {
+            checkIntArrayKey(key)
+            getPerSubCache(subId)[key] = IntArrayConfigValue(value)
+        }
+
+        @VisibleForTesting
         fun setStringForTest(subId: Int, key: String, value: String?) {
             check(key.endsWith("_string")) { "String key should ends with _string" }
             getPerSubCache(subId)[key] = StringConfigValue(value)
@@ -221,6 +268,8 @@
 
 private data class IntConfigValue(val value: Int) : ConfigValue
 
+private class IntArrayConfigValue(val value: IntArray?) : ConfigValue
+
 private data class StringConfigValue(val value: String?) : ConfigValue
 
 private typealias ConfigCache = ConcurrentHashMap<String, ConfigValue>
diff --git a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java
index 1a71e5a..d1988c4 100644
--- a/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java
+++ b/src/com/android/settings/network/telephony/Enhanced4gBasePreferenceController.java
@@ -34,7 +34,6 @@
 import androidx.preference.TwoStatePreference;
 
 import com.android.internal.telephony.flags.Flags;
-import com.android.internal.telephony.util.ArrayUtils;
 import com.android.settings.R;
 import com.android.settings.network.ims.VolteQueryImsState;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -56,8 +55,6 @@
     Preference mPreference;
     private PhoneCallStateTelephonyCallback mTelephonyCallback;
     private boolean mShow5gLimitedDialog;
-    boolean mIsNrEnabledFromCarrierConfig;
-    private boolean mHas5gCapability;
     private Integer mCallState;
     private final List<On4gLteUpdateListener> m4gLteListeners;
 
@@ -94,9 +91,6 @@
         mShow5gLimitedDialog = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL);
 
-        int[] nrAvailabilities = carrierConfig.getIntArray(
-                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
-        mIsNrEnabledFromCarrierConfig = !ArrayUtils.isEmpty(nrAvailabilities);
         return this;
     }
 
@@ -247,10 +241,6 @@
             }
             mTelephonyManager.registerTelephonyCallback(
                     mContext.getMainExecutor(), mTelephonyCallback);
-
-            final long supportedRadioBitmask = mTelephonyManager.getSupportedRadioAccessFamily();
-            mHas5gCapability =
-                    (supportedRadioBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0;
         }
 
         public void unregister() {
@@ -269,8 +259,7 @@
     }
 
     private boolean isDialogNeeded() {
-        Log.d(TAG, "Has5gCapability:" + mHas5gCapability);
-        return mShow5gLimitedDialog && mHas5gCapability && mIsNrEnabledFromCarrierConfig;
+        return mShow5gLimitedDialog && new NrRepository(mContext).isNrAvailable(mSubId);
     }
 
     private void show5gLimitedDialog(ImsMmTelManager imsMmTelManager) {
diff --git a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt
index 0d8766e..b79e818 100644
--- a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.kt
@@ -25,31 +25,28 @@
 import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
-import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
 import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
+import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
 import com.android.settings.spa.preference.ComposePreferenceController
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
 
-/**
- * Preference controller for "Voice over NR".
- */
-class NrAdvancedCallingPreferenceController @JvmOverloads constructor(
+/** Preference controller for "Voice over NR". */
+class NrAdvancedCallingPreferenceController
+@JvmOverloads
+constructor(
     context: Context,
     key: String,
-    private val callStateRepository : CallStateRepository = CallStateRepository(context),
+    private val voNrRepository: VoNrRepository = VoNrRepository(context),
+    private val callStateRepository: CallStateRepository = CallStateRepository(context),
 ) : ComposePreferenceController(context, key) {
     private var subId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
-    private var repository: VoNrRepository? = null
     private val searchItem = NrAdvancedCallingSearchItem(context)
 
     /** Initial this PreferenceController. */
-    @JvmOverloads
-    fun init(subId: Int, repository: VoNrRepository = VoNrRepository(mContext, subId)) {
+    fun init(subId: Int) {
         this.subId = subId
-        this.repository = repository
     }
 
     override fun getAvailabilityStatus() =
@@ -58,30 +55,32 @@
     @Composable
     override fun Content() {
         val summary = stringResource(R.string.nr_advanced_calling_summary)
-        val isInCall by remember { callStateRepository.isInCallFlow() }
-            .collectAsStateWithLifecycle(initialValue = false)
-        val isEnabled by remember {
-            repository?.isVoNrEnabledFlow() ?: flowOf(false)
-        }.collectAsStateWithLifecycle(initialValue = false)
+        val isInCall by
+            remember { callStateRepository.isInCallFlow() }
+                .collectAsStateWithLifecycle(initialValue = false)
+        val isVoNrEnabled by
+            remember { voNrRepository.isVoNrEnabledFlow(subId) }
+                .collectAsStateWithLifecycle(initialValue = false)
         val coroutineScope = rememberCoroutineScope()
-        SwitchPreference(object : SwitchPreferenceModel {
-            override val title = stringResource(R.string.nr_advanced_calling_title)
-            override val summary = { summary }
-            override val changeable = { !isInCall }
-            override val checked = { isEnabled }
-            override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
-                coroutineScope.launch {
-                    repository?.setVoNrEnabled(newChecked)
+        SwitchPreference(
+            object : SwitchPreferenceModel {
+                override val title = stringResource(R.string.nr_advanced_calling_title)
+                override val summary = { summary }
+                override val changeable = { !isInCall }
+                override val checked = { isVoNrEnabled }
+                override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
+                    coroutineScope.launch { voNrRepository.setVoNrEnabled(subId, newChecked) }
                 }
             }
-        })
+        )
     }
 
     companion object {
         class NrAdvancedCallingSearchItem(private val context: Context) :
             MobileNetworkSettingsSearchItem {
+            private val voNrRepository = VoNrRepository(context)
 
-            fun isAvailable(subId: Int): Boolean = VoNrRepository(context, subId).isVoNrAvailable()
+            fun isAvailable(subId: Int): Boolean = voNrRepository.isVoNrAvailable(subId)
 
             override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
                 if (!isAvailable(subId)) return null
diff --git a/src/com/android/settings/network/telephony/NrRepository.kt b/src/com/android/settings/network/telephony/NrRepository.kt
new file mode 100644
index 0000000..e6247fd
--- /dev/null
+++ b/src/com/android/settings/network/telephony/NrRepository.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.util.Log
+
+class NrRepository(private val context: Context) {
+    private val carrierConfigRepository = CarrierConfigRepository(context)
+
+    fun isNrAvailable(subId: Int): Boolean {
+        if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability(subId)) {
+            return false
+        }
+        val carrierNrAvailabilities =
+            carrierConfigRepository.getIntArray(
+                subId = subId,
+                key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+            )
+        return carrierNrAvailabilities?.isNotEmpty() ?: false
+    }
+
+    private fun has5gCapability(subId: Int): Boolean {
+        val telephonyManager = context.telephonyManager(subId)
+        return (telephonyManager.supportedRadioAccessFamily and
+                TelephonyManager.NETWORK_TYPE_BITMASK_NR > 0)
+            .also { Log.d(TAG, "[$subId] has5gCapability: $it") }
+    }
+
+    private companion object {
+        private const val TAG = "NrRepository"
+    }
+}
diff --git a/src/com/android/settings/network/telephony/VoNrRepository.kt b/src/com/android/settings/network/telephony/VoNrRepository.kt
index 35af284..635c572 100644
--- a/src/com/android/settings/network/telephony/VoNrRepository.kt
+++ b/src/com/android/settings/network/telephony/VoNrRepository.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionManager
-import android.telephony.TelephonyManager
 import android.util.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
@@ -29,43 +28,43 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.withContext
 
-class VoNrRepository(private val context: Context, private val subId: Int) {
-    private val telephonyManager = context.telephonyManager(subId)
-    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+class VoNrRepository(
+    private val context: Context,
+    private val nrRepository: NrRepository = NrRepository(context),
+) {
+    private val carrierConfigRepository = CarrierConfigRepository(context)
 
-    fun isVoNrAvailable(): Boolean {
-        if (!SubscriptionManager.isValidSubscriptionId(subId) || !has5gCapability()) return false
-        val carrierConfig = carrierConfigManager.safeGetConfig(
-            keys = listOf(
-                CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
-                CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
-                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
-            ),
-            subId = subId,
-        )
-        return carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL) &&
-            carrierConfig.getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL) &&
-            (carrierConfig.getIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)
-                ?.isNotEmpty() ?: false)
+    fun isVoNrAvailable(subId: Int): Boolean {
+        if (!nrRepository.isNrAvailable(subId)) return false
+        data class Config(val isVoNrEnabled: Boolean, val isVoNrSettingVisibility: Boolean)
+        val carrierConfig =
+            carrierConfigRepository.transformConfig(subId) {
+                Config(
+                    isVoNrEnabled = getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL),
+                    isVoNrSettingVisibility =
+                        getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
+                )
+            }
+        return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
     }
 
-    private fun has5gCapability() =
-        ((telephonyManager.supportedRadioAccessFamily and
-            TelephonyManager.NETWORK_TYPE_BITMASK_NR) > 0)
-            .also { Log.d(TAG, "[$subId] has5gCapability: $it") }
-
-    fun isVoNrEnabledFlow(): Flow<Boolean> = context.subscriptionsChangedFlow()
-        .map { telephonyManager.isVoNrEnabled }
-        .conflate()
-        .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
-        .flowOn(Dispatchers.Default)
-
-    suspend fun setVoNrEnabled(enabled: Boolean) = withContext(Dispatchers.Default) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext
-        val result = telephonyManager.setVoNrEnabled(enabled)
-        Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
+    fun isVoNrEnabledFlow(subId: Int): Flow<Boolean> {
+        val telephonyManager = context.telephonyManager(subId)
+        return context
+            .subscriptionsChangedFlow()
+            .map { telephonyManager.isVoNrEnabled }
+            .conflate()
+            .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
+            .flowOn(Dispatchers.Default)
     }
 
+    suspend fun setVoNrEnabled(subId: Int, enabled: Boolean) =
+        withContext(Dispatchers.Default) {
+            if (!SubscriptionManager.isValidSubscriptionId(subId)) return@withContext
+            val result = context.telephonyManager(subId).setVoNrEnabled(enabled)
+            Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
+        }
+
     private companion object {
         private const val TAG = "VoNrRepository"
     }
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index d7d1531..e987ebe 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -293,6 +293,7 @@
                     new ChooseLockSettingsHelper.Builder(this);
             launchedCDC = builder.setHeader(mTitle)
                     .setDescription(mDetails)
+                    .setAlternateButton(alternateButton)
                     .setExternal(true)
                     .setUserId(LockPatternUtils.USER_REPAIR_MODE)
                     .show();
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index 0d57b57..677d18e 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -197,8 +197,8 @@
         mCancelButton = view.findViewById(R.id.cancelButton);
         boolean showCancelButton = mRemoteValidation || getActivity().getIntent().getBooleanExtra(
                 SHOW_CANCEL_BUTTON, false);
-        boolean hasAlternateButton = (mFrp || mRemoteValidation) && !TextUtils.isEmpty(
-                mAlternateButtonText);
+        boolean hasAlternateButton = (mFrp || mRemoteValidation || mRepairMode)
+                && !TextUtils.isEmpty(mAlternateButtonText);
         mCancelButton.setVisibility(showCancelButton || hasAlternateButton
                 ? View.VISIBLE : View.GONE);
         if (hasAlternateButton) {
diff --git a/src/com/android/settings/users/MultiUserSwitchBarController.java b/src/com/android/settings/users/MultiUserSwitchBarController.java
index 07c03d7..1d64141 100644
--- a/src/com/android/settings/users/MultiUserSwitchBarController.java
+++ b/src/com/android/settings/users/MultiUserSwitchBarController.java
@@ -53,6 +53,12 @@
         mSwitchBar = switchBar;
         mListener = listener;
         mUserCapabilities = UserCapabilities.create(context);
+        updateState();
+        mSwitchBar.setListener(this);
+    }
+
+    void updateState() {
+        mUserCapabilities.updateAddUserCapabilities(mContext);
         mSwitchBar.setChecked(mUserCapabilities.mUserSwitcherEnabled);
 
         if (Flags.fixDisablingOfMuToggleWhenRestrictionApplied()) {
@@ -74,7 +80,6 @@
                 mSwitchBar.setEnabled(mUserCapabilities.mIsMain);
             }
         }
-        mSwitchBar.setListener(this);
     }
 
     @Override
@@ -92,7 +97,7 @@
         Log.d(TAG, "Toggling multi-user feature enabled state to: " + isChecked);
         final boolean success = Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.USER_SWITCHER_ENABLED, isChecked ? 1 : 0);
-        if (success && mListener != null) {
+        if (success && mListener != null && !Flags.newMultiuserSettingsUx()) {
             mListener.onMultiUserSwitchChanged(isChecked);
         }
         return success;
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index c387d9e..a0137df 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -419,6 +419,7 @@
                 mTimeoutToDockUserPreferenceController.getPreferenceKey()));
         mRemoveGuestOnExitPreferenceController.updateState(screen.findPreference(
                 mRemoveGuestOnExitPreferenceController.getPreferenceKey()));
+        mSwitchBarController.updateState();
         if (mShouldUpdateUserList) {
             updateUI();
         }
diff --git a/src/com/android/settings/webview/WebViewAppPicker.java b/src/com/android/settings/webview/WebViewAppPicker.java
index b1dfd14..7fb5ebc 100644
--- a/src/com/android/settings/webview/WebViewAppPicker.java
+++ b/src/com/android/settings/webview/WebViewAppPicker.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.os.UserManager;
 import android.text.TextUtils;
 import android.webkit.UserPackage;
 
@@ -149,17 +150,20 @@
     @VisibleForTesting
     String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper,
             Context context, String packageName) {
+        UserManager userManager = context.getSystemService(UserManager.class);
         List<UserPackage> userPackages =
                 webviewUpdateServiceWrapper.getPackageInfosAllUsers(context, packageName);
         for (UserPackage userPackage : userPackages) {
             if (!userPackage.isInstalledPackage()) {
                 // Package uninstalled/hidden
                 return context.getString(
-                        R.string.webview_uninstalled_for_user, userPackage.getUserInfo().name);
+                        R.string.webview_uninstalled_for_user,
+                        userManager.getUserInfo(userPackage.getUser().getIdentifier()).name);
             } else if (!userPackage.isEnabledPackage()) {
                 // Package disabled
                 return context.getString(
-                        R.string.webview_disabled_for_user, userPackage.getUserInfo().name);
+                        R.string.webview_disabled_for_user,
+                        userManager.getUserInfo(userPackage.getUser().getIdentifier()).name);
             }
         }
         return null;
diff --git a/src/com/android/settings/widget/TickButtonPreference.java b/src/com/android/settings/widget/TickButtonPreference.java
index b9b9b19..4778f8c 100644
--- a/src/com/android/settings/widget/TickButtonPreference.java
+++ b/src/com/android/settings/widget/TickButtonPreference.java
@@ -21,33 +21,30 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
+import com.android.settingslib.widget.TwoTargetPreference;
 
 /** A preference with tick icon. */
-public class TickButtonPreference extends Preference {
+public class TickButtonPreference extends TwoTargetPreference {
     private ImageView mCheckIcon;
     private boolean mIsSelected = false;
 
     public TickButtonPreference(Context context) {
         super(context);
-        init(context, null);
-    }
-
-    public TickButtonPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context, attrs);
-    }
-
-    private void init(Context context, AttributeSet attrs) {
-        setWidgetLayoutResource(R.layout.preference_check_icon);
     }
 
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
+        View divider =
+                holder.findViewById(
+                        com.android.settingslib.widget.preference.twotarget.R.id
+                                .two_target_divider);
+        if (divider != null) {
+            divider.setVisibility(View.GONE);
+        }
         mCheckIcon = (ImageView) holder.findViewById(R.id.check_icon);
         setSelected(mIsSelected);
     }
@@ -64,4 +61,10 @@
     public boolean isSelected() {
         return mIsSelected;
     }
+
+    @Override
+    protected int getSecondTargetResId() {
+        return R.layout.preference_check_icon;
+    }
+
 }
diff --git a/tests/Enable16KbTests/Android.bp b/tests/Enable16KbTests/Android.bp
index fa05d33..7e1d32c 100644
--- a/tests/Enable16KbTests/Android.bp
+++ b/tests/Enable16KbTests/Android.bp
@@ -34,8 +34,8 @@
     platform_apis: true,
     certificate: "platform",
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
     ],
 }
 
diff --git a/tests/anomaly-tester/Android.bp b/tests/anomaly-tester/Android.bp
index d437a6c..63af4ee 100644
--- a/tests/anomaly-tester/Android.bp
+++ b/tests/anomaly-tester/Android.bp
@@ -13,7 +13,7 @@
 
     certificate: "platform",
 
-    libs: ["android.test.runner"],
+    libs: ["android.test.runner.stubs.system"],
 
     static_libs: [
         "androidx.test.rules",
diff --git a/tests/perftests/Android.bp b/tests/perftests/Android.bp
index 7e3a8aa..b35bd4a 100644
--- a/tests/perftests/Android.bp
+++ b/tests/perftests/Android.bp
@@ -14,7 +14,7 @@
     certificate: "platform",
 
     libs: [
-        "android.test.runner",
+        "android.test.runner.stubs.system",
     ],
 
     static_libs: [
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index d6210e7..3fd4306 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -76,7 +76,7 @@
     ],
 
     libs: [
-        "android.test.mock",
+        "android.test.mock.impl",
         "ims-common",
     ],
 
diff --git a/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java
index 6969472..5486808 100644
--- a/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/CaptioningMoreOptionsFragmentTest.java
@@ -20,6 +20,9 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -27,15 +30,23 @@
 import com.android.settings.testutils.XmlTestUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /** Tests for {@link CaptioningMoreOptionsFragment}. */
 @RunWith(RobolectricTestRunner.class)
 public class CaptioningMoreOptionsFragmentTest {
+    // Language/locale preference key, from captioning_more_options.xml
+    private static final String CAPTIONING_LOCALE_KEY = "captioning_locale";
+
+    @Rule
+    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private CaptioningMoreOptionsFragment mFragment;
@@ -65,11 +76,40 @@
     @Test
     public void getNonIndexableKeys_existInXmlLayout() {
         final List<String> niks = CaptioningMoreOptionsFragment.SEARCH_INDEX_DATA_PROVIDER
-                .getNonIndexableKeys(mContext);
+                .getNonIndexableKeys(mContext)
+                .stream().filter(Objects::nonNull).collect(Collectors.toList());
         final List<String> keys =
-                XmlTestUtils.getKeysFromPreferenceXml(mContext,
-                        R.xml.captioning_more_options);
+                XmlTestUtils.getKeysFromPreferenceXml(mContext, R.xml.captioning_more_options);
 
         assertThat(keys).containsAtLeastElementsIn(niks);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+    public void getNonIndexableKeys_captioningEnabled_localeIsSearchable() {
+        setCaptioningEnabled(true);
+
+        final List<String> niks = CaptioningMoreOptionsFragment.SEARCH_INDEX_DATA_PROVIDER
+                .getNonIndexableKeys(mContext);
+
+        // Not in NonIndexableKeys == searchable
+        assertThat(niks).doesNotContain(CAPTIONING_LOCALE_KEY);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
+    public void getNonIndexableKeys_captioningDisabled_localeIsNotSearchable() {
+        setCaptioningEnabled(false);
+
+        final List<String> niks = CaptioningMoreOptionsFragment.SEARCH_INDEX_DATA_PROVIDER
+                .getNonIndexableKeys(mContext);
+
+        // In NonIndexableKeys == not searchable
+        assertThat(niks).contains(CAPTIONING_LOCALE_KEY);
+    }
+
+    private void setCaptioningEnabled(boolean enabled) {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, enabled ? 1 : 0);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
index 4b4dd8b..d137d82 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsProfilesControllerTest.java
@@ -120,11 +120,7 @@
                 .thenAnswer(invocation -> ImmutableList.of(mConnectableProfiles));
 
         setupDevice(mDeviceConfig);
-        mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
-                mCachedDevice, mLifecycle);
-        mProfiles.setKey(mController.getPreferenceKey());
-        mController.mProfilesContainer = mProfiles;
-        mScreen.addPreference(mProfiles);
+        initController(List.of());
         BluetoothProperties.le_audio_allow_list(Lists.newArrayList(LE_DEVICE_MODEL));
     }
 
@@ -554,6 +550,36 @@
 
     @Test
     public void prefKeyInBlockingList_hideToggle() {
+        initController(List.of("A2DP"));
+        setupDevice(makeDefaultDeviceConfig());
+
+        addA2dpProfileToDevice(true, true, true);
+        when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any()))
+                .thenReturn(ImmutableSet.of());
+
+        showScreen(mController);
+
+        List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
+        assertThat(switches.get(0).isVisible()).isFalse();
+    }
+
+    @Test
+    public void prefKeyNotInBlockingList_showToggle() {
+        initController(List.of());
+        setupDevice(makeDefaultDeviceConfig());
+
+        addA2dpProfileToDevice(true, true, true);
+        when(mFeatureProvider.getInvisibleProfilePreferenceKeys(any(), any()))
+                .thenReturn(ImmutableSet.of());
+
+        showScreen(mController);
+
+        List<SwitchPreferenceCompat> switches = getProfileSwitches(false);
+        assertThat(switches.get(0).isVisible()).isTrue();
+    }
+
+    @Test
+    public void prefKeyInFeatureProviderBlockingList_hideToggle() {
         setupDevice(makeDefaultDeviceConfig());
 
         addA2dpProfileToDevice(true, true, true);
@@ -567,7 +593,7 @@
     }
 
     @Test
-    public void prefKeyNotInBlockingList_showToggle() {
+    public void prefKeyNotInFeatureProviderBlockingList_showToggle() {
         setupDevice(makeDefaultDeviceConfig());
 
         addA2dpProfileToDevice(true, true, true);
@@ -627,4 +653,13 @@
         assertThat(switches.getFirst().getTitle()).isEqualTo(
                 mContext.getString(mLeAudioProfile.getNameResource(mDevice)));
     }
+
+    private void initController(List<String> invisibleProfiles) {
+        mController = new BluetoothDetailsProfilesController(mContext, mFragment, mLocalManager,
+                mCachedDevice, mLifecycle, invisibleProfiles);
+        mProfiles.setKey(mController.getPreferenceKey());
+        mController.mProfilesContainer = mProfiles;
+        mScreen.removeAll();
+        mScreen.addPreference(mProfiles);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
index a5bc246..8070b2e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
@@ -122,10 +122,11 @@
                 .thenReturn(
                     DeviceSettingConfigModel(
                         listOf(
-                            DeviceSettingConfigItemModel.BuiltinItem(
+                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
-                                "bluetooth_device_header"),
-                            DeviceSettingConfigItemModel.BuiltinItem(
+                                "bluetooth_device_header"
+                            ),
+                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons"),
                         ),
                         listOf(),
@@ -203,10 +204,10 @@
                 .thenReturn(
                     DeviceSettingConfigModel(
                         listOf(
-                            DeviceSettingConfigItemModel.BuiltinItem(
+                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
                                 "bluetooth_device_header"),
-                            DeviceSettingConfigItemModel.BuiltinItem(
+                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
                                 "keyboard_settings"),
                         ),
@@ -227,12 +228,12 @@
                 .thenReturn(
                     DeviceSettingConfigModel(
                         listOf(
-                            DeviceSettingConfigItemModel.BuiltinItem(
+                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_HEADER,
                                 "bluetooth_device_header"),
                             DeviceSettingConfigItemModel.AppProvidedItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_ANC),
-                            DeviceSettingConfigItemModel.BuiltinItem(
+                            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                                 DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
                                 "keyboard_settings"),
                         ),
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
index 9cbe6e3..6869c23 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
@@ -282,10 +282,10 @@
 
     private companion object {
         val BUILTIN_SETTING_ITEM_1 =
-            DeviceSettingConfigItemModel.BuiltinItem(
+            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                 DeviceSettingId.DEVICE_SETTING_ID_HEADER, "bluetooth_device_header")
         val BUILDIN_SETTING_ITEM_2 =
-            DeviceSettingConfigItemModel.BuiltinItem(
+            DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem(
                 DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, "action_buttons")
         val SETTING_ITEM_HELP = DeviceSettingConfigItemModel.AppProvidedItem(12345)
     }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
new file mode 100644
index 0000000..7f17291
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothStatusCodes;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class})
+public class AudioSharingIncompatibleDialogFragmentTest {
+    private static final String TEST_DEVICE_NAME = "test";
+    private static final AudioSharingIncompatibleDialogFragment.DialogEventListener
+            EMPTY_EVENT_LISTENER = () -> {};
+
+    @Rule public final MockitoRule mocks = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
+    private Fragment mParent;
+    private AudioSharingIncompatibleDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        ShadowAlertDialogCompat.reset();
+        ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(
+                BluetoothAdapter.getDefaultAdapter());
+        shadowBluetoothAdapter.setEnabled(true);
+        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME);
+        mFragment = new AudioSharingIncompatibleDialogFragment();
+        mParent = new Fragment();
+        FragmentController.setupFragment(mParent, FragmentActivity.class, /* containerViewId= */
+                0, /* bundle= */ null);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowAlertDialogCompat.reset();
+    }
+
+    @Test
+    public void getMetricsCategory_correctValue() {
+        // TODO: update to real metrics id
+        assertThat(mFragment.getMetricsCategory()).isEqualTo(0);
+    }
+
+    @Test
+    public void onCreateDialog_flagOff_dialogNotExist() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+                EMPTY_EVENT_LISTENER);
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+    }
+
+    @Test
+    public void onCreateDialog_unattachedFragment_dialogNotExist() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingIncompatibleDialogFragment.show(new Fragment(), mCachedBluetoothDevice,
+                EMPTY_EVENT_LISTENER);
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+    }
+
+    @Test
+    public void onCreateDialog_flagOn_showDialog() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+                EMPTY_EVENT_LISTENER);
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+        assertThat(dialog.isShowing()).isTrue();
+        TextView title = dialog.findViewById(R.id.title_text);
+        assertThat(title).isNotNull();
+        // TODO: use string res
+        assertThat(title.getText().toString()).isEqualTo(
+                "Can't share audio with " + TEST_DEVICE_NAME);
+    }
+
+    @Test
+    public void onCreateDialog_clickBtn_callbackTriggered() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AtomicBoolean isBtnClicked = new AtomicBoolean(false);
+        AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+                () -> isBtnClicked.set(true));
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+        View btnView = dialog.findViewById(android.R.id.button1);
+        assertThat(btnView).isNotNull();
+        btnView.performClick();
+        shadowMainLooper().idle();
+        assertThat(dialog.isShowing()).isFalse();
+        assertThat(isBtnClicked.get()).isTrue();
+    }
+}
+
+
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragmentTest.java
new file mode 100644
index 0000000..4b0524b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingRetryDialogFragmentTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothStatusCodes;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.View;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+        shadows = {
+                ShadowAlertDialogCompat.class,
+                ShadowBluetoothAdapter.class,
+        })
+public class AudioSharingRetryDialogFragmentTest {
+    @Rule
+    public final MockitoRule mocks = MockitoJUnit.rule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    private Fragment mParent;
+    private AudioSharingRetryDialogFragment mFragment;
+
+    @Before
+    public void setUp() {
+        ShadowAlertDialogCompat.reset();
+        ShadowBluetoothAdapter shadowBluetoothAdapter =
+                Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        shadowBluetoothAdapter.setEnabled(true);
+        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mFragment = new AudioSharingRetryDialogFragment();
+        mParent = new Fragment();
+        FragmentController.setupFragment(
+                mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowAlertDialogCompat.reset();
+    }
+
+    @Test
+    public void getMetricsCategory_correctValue() {
+        // TODO: update metrics id
+        assertThat(mFragment.getMetricsCategory())
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void onCreateDialog_flagOff_dialogNotExist() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingRetryDialogFragment.show(mParent);
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+    }
+
+    @Test
+    public void onCreateDialog_unattachedFragment_dialogNotExist() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingRetryDialogFragment.show(new Fragment());
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNull();
+    }
+
+    @Test
+    public void onCreateDialog_flagOn_showDialog() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingRetryDialogFragment.show(mParent);
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+        assertThat(dialog.isShowing()).isTrue();
+    }
+
+    @Test
+    public void onCreateDialog_clickOk_dialogDismiss() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AudioSharingRetryDialogFragment.show(mParent);
+        shadowMainLooper().idle();
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+        View btnView = dialog.findViewById(android.R.id.button1);
+        assertThat(btnView).isNotNull();
+        btnView.performClick();
+        shadowMainLooper().idle();
+        assertThat(dialog.isShowing()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
index d1533c9..354c5c7 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -111,10 +111,10 @@
 @RunWith(RobolectricTestRunner.class)
 @Config(
         shadows = {
-            ShadowBluetoothAdapter.class,
-            ShadowBluetoothUtils.class,
-            ShadowThreadUtils.class,
-            ShadowAlertDialogCompat.class
+                ShadowBluetoothAdapter.class,
+                ShadowBluetoothUtils.class,
+                ShadowThreadUtils.class,
+                ShadowAlertDialogCompat.class
         })
 public class AudioSharingSwitchBarControllerTest {
     private static final String TEST_DEVICE_NAME1 = "test1";
@@ -123,12 +123,13 @@
     private static final String TEST_DEVICE_ANONYMIZED_ADDR2 = "XX:XX:02";
     private static final int TEST_DEVICE_GROUP_ID1 = 1;
     private static final int TEST_DEVICE_GROUP_ID2 = 2;
-    private static final Correspondence<Fragment, String> TAG_EQUALS =
+    private static final Correspondence<Fragment, String> CLAZZNAME_EQUALS =
             Correspondence.from(
-                    (Fragment fragment, String tag) ->
+                    (Fragment fragment, String clazzName) ->
                             fragment instanceof DialogFragment
-                                    && ((DialogFragment) fragment).getTag() != null
-                                    && ((DialogFragment) fragment).getTag().equals(tag),
+                                    && ((DialogFragment) fragment).getClass().getName() != null
+                                    && ((DialogFragment) fragment).getClass().getName().equals(
+                                    clazzName),
                     "is equal to");
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -344,6 +345,18 @@
     }
 
     @Test
+    public void onStart_flagOn_updateSwitch() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mBroadcast.isEnabled(null)).thenReturn(false);
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of());
+        mController.onStart(mLifecycleOwner);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(mSwitchBar.isChecked()).isFalse();
+        assertThat(mSwitchBar.isEnabled()).isTrue();
+    }
+
+    @Test
     public void onStop_flagOff_doNothing() {
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         mController.onStop(mLifecycleOwner);
@@ -398,15 +411,21 @@
     }
 
     @Test
-    public void onCheckedChangedToChecked_noConnectedLeaDevices_flagOn_notStartAudioSharing() {
+    public void onCheckedChangedToChecked_noConnectedLeaDevices_flagOn_showDialog() {
         FeatureFlagUtils.setEnabled(
                 mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
         when(mBtnView.isEnabled()).thenReturn(true);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of());
         doNothing().when(mBroadcast).startPrivateBroadcast();
         mController.onCheckedChanged(mBtnView, /* isChecked= */ true);
+        shadowOf(Looper.getMainLooper()).idle();
+
         assertThat(mSwitchBar.isChecked()).isFalse();
         verify(mBroadcast, never()).startPrivateBroadcast();
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments)
+                .comparingElementsUsing(CLAZZNAME_EQUALS)
+                .containsExactly(AudioSharingConfirmDialogFragment.class.getName());
     }
 
     @Test
@@ -526,8 +545,8 @@
 
         List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
         assertThat(childFragments)
-                .comparingElementsUsing(TAG_EQUALS)
-                .containsExactly(AudioSharingDialogFragment.tag());
+                .comparingElementsUsing(CLAZZNAME_EQUALS)
+                .containsExactly(AudioSharingDialogFragment.class.getName());
 
         AudioSharingDialogFragment fragment =
                 (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments);
@@ -613,6 +632,8 @@
         mSwitchBar.setChecked(false);
         when(mBroadcast.isEnabled(any())).thenReturn(false);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2));
+        when(mDeviceManager.getCachedDevicesCopy()).thenReturn(
+                ImmutableList.of(mCachedDevice1, mCachedDevice2));
         mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 1);
         shadowOf(Looper.getMainLooper()).idle();
         assertThat(mSwitchBar.isChecked()).isFalse();
@@ -706,6 +727,8 @@
         mSwitchBar.setEnabled(false);
         when(mBroadcast.isEnabled(null)).thenReturn(false);
         when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1));
+        when(mDeviceManager.getCachedDevicesCopy()).thenReturn(
+                ImmutableList.of(mCachedDevice2, mCachedDevice1));
         mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.LE_AUDIO);
         shadowOf(Looper.getMainLooper()).idle();
         assertThat(mSwitchBar.isChecked()).isFalse();
@@ -713,6 +736,22 @@
     }
 
     @Test
+    public void onActiveDeviceChanged_a2dpProfile_updateSwitch() {
+        mSwitchBar.setChecked(true);
+        mSwitchBar.setEnabled(false);
+        when(mBroadcast.isEnabled(null)).thenReturn(false);
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2));
+        when(mCachedDevice2.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false);
+        when(mCachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
+        when(mDeviceManager.getCachedDevicesCopy()).thenReturn(
+                ImmutableList.of(mCachedDevice1, mCachedDevice2));
+        mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.A2DP);
+        shadowOf(Looper.getMainLooper()).idle();
+        assertThat(mSwitchBar.isChecked()).isFalse();
+        verify(mSwitchBar).setEnabled(true);
+    }
+
+    @Test
     public void onActiveDeviceChanged_nullActiveDevice_doNothing() {
         mController.onActiveDeviceChanged(/* activeDevice= */ null, BluetoothProfile.LE_AUDIO);
         shadowOf(Looper.getMainLooper()).idle();
@@ -721,14 +760,6 @@
     }
 
     @Test
-    public void onActiveDeviceChanged_notLeaProfile_doNothing() {
-        mController.onActiveDeviceChanged(mCachedDevice2, BluetoothProfile.HEADSET);
-        shadowOf(Looper.getMainLooper()).idle();
-        verify(mSwitchBar, never()).setEnabled(anyBoolean());
-        verify(mSwitchBar, never()).setChecked(anyBoolean());
-    }
-
-    @Test
     public void testAccessibilityDelegate() {
         View view = new View(mContext);
         AccessibilityEvent event =
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
index d35b608..9b5f452 100644
--- a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
@@ -35,8 +35,8 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.UserInfo;
 import android.graphics.drawable.ColorDrawable;
+import android.os.UserHandle;
 import android.webkit.UserPackage;
 
 import androidx.fragment.app.FragmentActivity;
@@ -71,15 +71,17 @@
 })
 public class WebViewAppPickerTest {
 
-    private final static String PACKAGE_NAME = "com.example.test";
-    private final static String PACKAGE_VERSION = "1.0.0";
+    private static final String PACKAGE_NAME = "com.example.test";
+    private static final String PACKAGE_VERSION = "1.0.0";
+    private static final String FIRST_USER_NAME = "FIRST_USER";
+    private static final String SECOND_USER_NAME = "SECOND_USER";
 
     @Mock
     private FragmentActivity mActivity;
 
     private Context mContext;
-    private UserInfo mFirstUser;
-    private UserInfo mSecondUser;
+    private UserHandle mFirstUser;
+    private UserHandle mSecondUser;
     private ShadowPackageManager mPackageManager;
     private WebViewAppPicker mPicker;
     private WebViewUpdateServiceWrapper mWvusWrapper;
@@ -105,8 +107,8 @@
         mPackageManager.addPackage(packageInfo);
         mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable());
 
-        mFirstUser = new UserInfo(0, "FIRST_USER", 0);
-        mSecondUser = new UserInfo(0, "SECOND_USER", 0);
+        mFirstUser = mUserManager.addUser(0, FIRST_USER_NAME, 0);
+        mSecondUser = mUserManager.addUser(1, SECOND_USER_NAME, 0);
         mPicker = new WebViewAppPicker();
         mPicker = spy(mPicker);
         doNothing().when(mPicker).updateCandidates();
@@ -238,13 +240,13 @@
         UserPackage packageForFirstUser = mock(UserPackage.class);
         when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
         when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
-        when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
+        when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
 
         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
                 .thenReturn(Collections.singletonList(packageForFirstUser));
 
-        final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name);
+        final String expectedReason = String.format("(disabled for user %s)", FIRST_USER_NAME);
         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
                 .isEqualTo(expectedReason);
     }
@@ -254,13 +256,13 @@
         UserPackage packageForFirstUser = mock(UserPackage.class);
         when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
         when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
-        when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
+        when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
 
         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
                 .thenReturn(Collections.singletonList(packageForFirstUser));
 
-        final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name);
+        final String expectedReason = String.format("(uninstalled for user %s)", FIRST_USER_NAME);
         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
                 .isEqualTo(expectedReason);
     }
@@ -270,18 +272,18 @@
         UserPackage packageForFirstUser = mock(UserPackage.class);
         when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
         when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
-        when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
+        when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
 
         UserPackage packageForSecondUser = mock(UserPackage.class);
         when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
         when(packageForSecondUser.isInstalledPackage()).thenReturn(false);
-        when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser);
+        when(packageForSecondUser.getUser()).thenReturn(mSecondUser);
 
         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
                 .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
 
-        final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name);
+        final String expectedReason = String.format("(disabled for user %s)", FIRST_USER_NAME);
         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
                 .isEqualTo(expectedReason);
     }
@@ -295,18 +297,18 @@
         UserPackage packageForFirstUser = mock(UserPackage.class);
         when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
         when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
-        when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
+        when(packageForFirstUser.getUser()).thenReturn(mFirstUser);
 
         UserPackage packageForSecondUser = mock(UserPackage.class);
         when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
         when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
-        when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser);
+        when(packageForSecondUser.getUser()).thenReturn(mSecondUser);
 
         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
                 .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
 
-        final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name);
+        final String expectedReason = String.format("(uninstalled for user %s)", FIRST_USER_NAME);
         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
                 .isEqualTo(expectedReason);
     }
diff --git a/tests/screenshot/Android.bp b/tests/screenshot/Android.bp
index 5130fe5..f79d00f 100644
--- a/tests/screenshot/Android.bp
+++ b/tests/screenshot/Android.bp
@@ -66,9 +66,9 @@
 
     ],
     libs: [
-        "android.test.runner",
-        "android.test.base",
-        "android.test.mock",
+        "android.test.runner.impl",
+        "android.test.base.impl",
+        "android.test.mock.impl",
         "truth",
     ],
 
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt
index 8c54751..12bbcaf 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/CarrierConfigRepositoryTest.kt
@@ -78,6 +78,19 @@
     }
 
     @Test
+    fun getIntArray_returnValue() {
+        val key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY
+        mockCarrierConfigManager.stub {
+            on { getConfigForSubId(any(), eq(key)) } doReturn
+                persistableBundleOf(key to intArrayOf(99))
+        }
+
+        val value = repository.getIntArray(SUB_ID, key)!!.toList()
+
+        assertThat(value).containsExactly(99)
+    }
+
+    @Test
     fun getString_returnValue() {
         val key = CarrierConfigManager.KEY_CARRIER_NAME_STRING
         mockCarrierConfigManager.stub {
@@ -104,7 +117,8 @@
         assertThat(carrierName)
             .isEqualTo(
                 CarrierConfigManager.getDefaultConfig()
-                    .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT))
+                    .getInt(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)
+            )
     }
 
     @Test
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt
index 418a00b..c8fc977 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.kt
@@ -41,91 +41,77 @@
 
 @RunWith(AndroidJUnit4::class)
 class NrAdvancedCallingPreferenceControllerTest {
-    @get:Rule
-    val composeTestRule = createComposeRule()
+    @get:Rule val composeTestRule = createComposeRule()
 
     private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
 
-    private val callStateRepository = mock<CallStateRepository> {
-        on { isInCallFlow() } doReturn flowOf(false)
-    }
+    private val callStateRepository =
+        mock<CallStateRepository> { on { isInCallFlow() } doReturn flowOf(false) }
 
-    private val voNrRepository = mock<VoNrRepository>()
+    private val voNrRepository =
+        mock<VoNrRepository> { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) }
 
-    private val controller = NrAdvancedCallingPreferenceController(
-        context = context,
-        key = TEST_KEY,
-        callStateRepository = callStateRepository,
-    ).apply { init(SUB_ID, voNrRepository) }
+    private val controller =
+        NrAdvancedCallingPreferenceController(
+                context = context,
+                key = TEST_KEY,
+                voNrRepository = voNrRepository,
+                callStateRepository = callStateRepository,
+            )
+            .apply { init(SUB_ID) }
 
     @Test
     fun isChecked_voNrEnabled_on() {
-        voNrRepository.stub {
-            on { isVoNrEnabledFlow() } doReturn flowOf(true)
-        }
+        voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(true) }
 
-        composeTestRule.setContent {
-            controller.Content()
-        }
+        composeTestRule.setContent { controller.Content() }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
+        composeTestRule
+            .onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
             .assertIsOn()
     }
 
     @Test
     fun isChecked_voNrDisabled_off() {
-        voNrRepository.stub {
-            on { isVoNrEnabledFlow() } doReturn flowOf(false)
-        }
+        voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) }
 
-        composeTestRule.setContent {
-            controller.Content()
-        }
+        composeTestRule.setContent { controller.Content() }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
+        composeTestRule
+            .onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
             .assertIsOff()
     }
 
     @Test
-    fun isEnabled_notInCall_enabled() {
-        callStateRepository.stub {
-            on { isInCallFlow() } doReturn flowOf(false)
-        }
+    fun isChangeable_notInCall_changeable() {
+        callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(false) }
 
-        composeTestRule.setContent {
-            controller.Content()
-        }
+        composeTestRule.setContent { controller.Content() }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
+        composeTestRule
+            .onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
             .assertIsEnabled()
     }
 
     @Test
-    fun isEnabled_inCall_notEnabled() {
-        callStateRepository.stub {
-            on { isInCallFlow() } doReturn flowOf(true)
-        }
+    fun isChangeable_inCall_notChangeable() {
+        callStateRepository.stub { on { isInCallFlow() } doReturn flowOf(true) }
 
-        composeTestRule.setContent {
-            controller.Content()
-        }
+        composeTestRule.setContent { controller.Content() }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
+        composeTestRule
+            .onNodeWithText(context.getString(R.string.nr_advanced_calling_title))
             .assertIsNotEnabled()
     }
 
     @Test
     fun onClick_setVoNrEnabled(): Unit = runBlocking {
-        voNrRepository.stub {
-            on { isVoNrEnabledFlow() } doReturn flowOf(false)
-        }
+        voNrRepository.stub { on { isVoNrEnabledFlow(SUB_ID) } doReturn flowOf(false) }
 
-        composeTestRule.setContent {
-            controller.Content()
-        }
+        composeTestRule.setContent { controller.Content() }
         composeTestRule.onRoot().performClick()
 
-        verify(voNrRepository).setVoNrEnabled(true)
+        verify(voNrRepository).setVoNrEnabled(SUB_ID, true)
     }
 
     private companion object {
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt
new file mode 100644
index 0000000..2df2a0f
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/NrRepositoryTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.TelephonyManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class NrRepositoryTest {
+    private val mockTelephonyManager =
+        mock<TelephonyManager> {
+            on { createForSubscriptionId(SUB_ID) } doReturn mock
+            on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR
+        }
+
+    private val context: Context =
+        spy(ApplicationProvider.getApplicationContext()) {
+            on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+        }
+
+    private val repository = NrRepository(context)
+
+    @Before
+    fun setUp() {
+        CarrierConfigRepository.resetForTest()
+    }
+
+    @Test
+    fun isNrAvailable_deviceNoNr_returnFalse() {
+        mockTelephonyManager.stub {
+            on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+        }
+
+        val available = repository.isNrAvailable(SUB_ID)
+
+        assertThat(available).isFalse()
+    }
+
+    @Test
+    fun isNrAvailable_carrierConfigNrIsEmpty_returnFalse() {
+        CarrierConfigRepository.setIntArrayForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+            value = intArrayOf(),
+        )
+
+        val available = repository.isNrAvailable(SUB_ID)
+
+        assertThat(available).isFalse()
+    }
+
+    @Test
+    fun isNrAvailable_carrierConfigNrIsNull_returnFalse() {
+        CarrierConfigRepository.setIntArrayForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+            value = null,
+        )
+
+        val available = repository.isNrAvailable(SUB_ID)
+
+        assertThat(available).isFalse()
+    }
+
+    @Test
+    fun isNrAvailable_allEnabled_returnTrue() {
+        mockTelephonyManager.stub {
+            on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR
+        }
+        CarrierConfigRepository.setIntArrayForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
+            value = intArrayOf(1, 2),
+        )
+
+        val available = repository.isNrAvailable(SUB_ID)
+
+        assertThat(available).isTrue()
+    }
+
+    private companion object {
+        const val SUB_ID = 10
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt
index 9c20afe..90d0aa5 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/VoNrRepositoryTest.kt
@@ -19,17 +19,15 @@
 import android.content.Context
 import android.telephony.CarrierConfigManager
 import android.telephony.TelephonyManager
-import androidx.core.os.persistableBundleOf
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.anyVararg
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.stub
@@ -38,127 +36,107 @@
 @RunWith(AndroidJUnit4::class)
 class VoNrRepositoryTest {
 
-    private val mockTelephonyManager = mock<TelephonyManager> {
-        on { createForSubscriptionId(SUB_ID) } doReturn mock
-        on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR
+    private val mockTelephonyManager =
+        mock<TelephonyManager> { on { createForSubscriptionId(SUB_ID) } doReturn mock }
+
+    private val context: Context =
+        spy(ApplicationProvider.getApplicationContext()) {
+            on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+        }
+
+    private val mockNrRepository = mock<NrRepository> { on { isNrAvailable(SUB_ID) } doReturn true }
+
+    private val repository = VoNrRepository(context, mockNrRepository)
+
+    @Before
+    fun setUp() {
+        CarrierConfigRepository.resetForTest()
+        CarrierConfigRepository.setBooleanForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
+            value = true,
+        )
+        CarrierConfigRepository.setBooleanForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
+            value = true,
+        )
     }
 
-    private val carrierConfig = persistableBundleOf(
-        CarrierConfigManager.KEY_VONR_ENABLED_BOOL to true,
-        CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL to true,
-        CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY to intArrayOf(1, 2),
-    )
-
-    private val mockCarrierConfigManager = mock<CarrierConfigManager> {
-        on { getConfigForSubId(eq(SUB_ID), anyVararg()) } doReturn carrierConfig
-    }
-
-    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
-        on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
-        on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
-    }
-
-    private val repository = VoNrRepository(context, SUB_ID)
-
     @Test
     fun isVoNrAvailable_visibleDisable_returnFalse() {
-        carrierConfig.apply {
-            putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, false)
-        }
+        CarrierConfigRepository.setBooleanForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
+            value = false,
+        )
 
-        val available = repository.isVoNrAvailable()
+        val available = repository.isVoNrAvailable(SUB_ID)
 
         assertThat(available).isFalse()
     }
 
     @Test
     fun isVoNrAvailable_voNrDisabled_returnFalse() {
-        carrierConfig.apply {
-            putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, false)
-        }
+        CarrierConfigRepository.setBooleanForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
+            value = false,
+        )
 
-        val available = repository.isVoNrAvailable()
+        val available = repository.isVoNrAvailable(SUB_ID)
 
         assertThat(available).isFalse()
     }
 
     @Test
     fun isVoNrAvailable_allEnabled_returnTrue() {
-        mockTelephonyManager.stub {
-            on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_NR
-        }
-        carrierConfig.apply {
-            putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, true)
-            putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, true)
-            putIntArray(
-                CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
-                intArrayOf(1, 2),
-            )
-        }
+        CarrierConfigRepository.setBooleanForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_VONR_ENABLED_BOOL,
+            value = true,
+        )
+        CarrierConfigRepository.setBooleanForTest(
+            subId = SUB_ID,
+            key = CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL,
+            value = true,
+        )
 
-        val available = repository.isVoNrAvailable()
+        val available = repository.isVoNrAvailable(SUB_ID)
 
         assertThat(available).isTrue()
     }
 
     @Test
-    fun isVoNrAvailable_deviceNoNr_returnFalse() {
-        mockTelephonyManager.stub {
-            on { supportedRadioAccessFamily } doReturn TelephonyManager.NETWORK_TYPE_BITMASK_LTE
-        }
+    fun isVoNrAvailable_noNr_returnFalse() {
+        mockNrRepository.stub { on { isNrAvailable(SUB_ID) } doReturn false }
 
-        val available = repository.isVoNrAvailable()
-
-        assertThat(available).isFalse()
-    }
-
-    @Test
-    fun isVoNrAvailable_carrierNoNr_returnFalse() {
-        carrierConfig.apply {
-            putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, intArrayOf())
-        }
-
-        val available = repository.isVoNrAvailable()
-
-        assertThat(available).isFalse()
-    }
-
-    @Test
-    fun isVoNrAvailable_carrierConfigNrIsNull_returnFalse() {
-        carrierConfig.apply {
-            putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, null)
-        }
-
-        val available = repository.isVoNrAvailable()
+        val available = repository.isVoNrAvailable(SUB_ID)
 
         assertThat(available).isFalse()
     }
 
     @Test
     fun isVoNrEnabledFlow_voNrDisabled() = runBlocking {
-        mockTelephonyManager.stub {
-            on { isVoNrEnabled } doReturn false
-        }
+        mockTelephonyManager.stub { on { isVoNrEnabled } doReturn false }
 
-        val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull()
+        val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
 
         assertThat(isVoNrEnabled).isFalse()
     }
 
     @Test
     fun isVoNrEnabledFlow_voNrEnabled() = runBlocking {
-        mockTelephonyManager.stub {
-            on { isVoNrEnabled } doReturn true
-        }
+        mockTelephonyManager.stub { on { isVoNrEnabled } doReturn true }
 
-        val isVoNrEnabled = repository.isVoNrEnabledFlow().firstWithTimeoutOrNull()
+        val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
 
         assertThat(isVoNrEnabled).isTrue()
     }
 
     @Test
     fun setVoNrEnabled(): Unit = runBlocking {
-        repository.setVoNrEnabled(true)
+        repository.setVoNrEnabled(SUB_ID, true)
 
         verify(mockTelephonyManager).setVoNrEnabled(true)
     }
diff --git a/tests/uitests/Android.bp b/tests/uitests/Android.bp
index d5852b3..be402f4 100644
--- a/tests/uitests/Android.bp
+++ b/tests/uitests/Android.bp
@@ -33,8 +33,8 @@
     ],
 
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs.system",
+        "android.test.base.stubs.system",
     ],
 
     static_libs: [