Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fd40e90..cc4d898 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5362,6 +5362,14 @@
             </intent-filter>
         </receiver>
 
+        <receiver
+            android:name=".shortcut.ShortcutsUpdateReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+
         <!-- This is the longest AndroidManifest.xml ever. -->
     </application>
 </manifest>
diff --git a/aconfig/catalyst/about_phone.aconfig b/aconfig/catalyst/about_phone.aconfig
index 0863041..5403b65 100644
--- a/aconfig/catalyst/about_phone.aconfig
+++ b/aconfig/catalyst/about_phone.aconfig
@@ -7,3 +7,11 @@
   description: "Flag for Legal information"
   bug: "323791114"
 }
+
+flag {
+  name: "catalyst_firmware_version"
+  namespace: "android_settings"
+  description: "Flag for Android version"
+  bug: "323791114"
+}
+
diff --git a/res/drawable/ic_android_satellite_24px.xml b/res/drawable/ic_android_satellite_24px.xml
index 15f2884..b08f19b 100644
--- a/res/drawable/ic_android_satellite_24px.xml
+++ b/res/drawable/ic_android_satellite_24px.xml
@@ -3,7 +3,7 @@
     android:height="24dp"
     android:viewportWidth="960"
     android:viewportHeight="960"
-    android:tint="?attr/colorControlNormal">
+    android:tint="?android:attr/colorControlNormal">
   <path
       android:fillColor="@android:color/white"
       android:pathData="M487,600L392,505L346,551L376,580Q399,603 399,637Q399,671 376,694L333,737Q310,760 276.5,760Q243,760 220,737L103,620Q80,597 80,563.5Q80,530 103,507L146,464Q169,441 203,441Q237,441 260,464L289,494L335,448L160,273L273,160L448,335L493,290L464,260Q441,237 441,203Q441,169 464,146L507,103Q530,80 563.5,80Q597,80 620,103L737,220Q760,243 760,276.5Q760,310 737,333L694,376Q671,399 637,399Q603,399 580,376L550,347L505,392L600,487L487,600ZM520,880L520,800Q637,800 718.5,718.5Q800,637 800,520L880,520Q880,595 851.5,660.5Q823,726 774.5,774.5Q726,823 660.5,851.5Q595,880 520,880ZM520,720L520,640Q570,640 605,605Q640,570 640,520L720,520Q720,603 661.5,661.5Q603,720 520,720ZM520,203L550,233L593,190L563,160Q563,160 563,160Q563,160 563,160L520,203Q520,203 520,203Q520,203 520,203ZM160,563L190,593L233,550L203,520Q203,520 203,520Q203,520 203,520L160,563Q160,563 160,563Q160,563 160,563ZM637,320L680,277Q680,277 680,277Q680,277 680,277L650,247L607,290L637,320Q637,320 637,320Q637,320 637,320ZM277,680L320,637Q320,637 320,637Q320,637 320,637L290,607L247,650L277,680Q277,680 277,680Q277,680 277,680Z"/>
diff --git a/res/values-eu/arrays.xml b/res/values-eu/arrays.xml
index d19aa7f..4ff40ac 100644
--- a/res/values-eu/arrays.xml
+++ b/res/values-eu/arrays.xml
@@ -506,7 +506,7 @@
     <item msgid="2548100558260478605">"Pertsona taldea"</item>
     <item msgid="2270003903304578284">"Bonbilla"</item>
     <item msgid="4793496619091161864">"Egutegia"</item>
-    <item msgid="5673625795644364100">"Ez molestatzeko modua"</item>
+    <item msgid="5673625795644364100">"Ez molestatzeko"</item>
     <item msgid="1423820834865831361">"Pertsona bat korrika"</item>
     <item msgid="2037298830718732608">"Golfa"</item>
     <item msgid="2197835014443491074">"Gimnasioko pisua"</item>
diff --git a/res/values-gl/arrays.xml b/res/values-gl/arrays.xml
index 055461f..49cbf33 100644
--- a/res/values-gl/arrays.xml
+++ b/res/values-gl/arrays.xml
@@ -53,10 +53,10 @@
     <item msgid="1656176059757237036">"Conectando..."</item>
     <item msgid="3249903732481917388">"Autenticando…"</item>
     <item msgid="7789156794775399931">"Obtendo enderezo IP..."</item>
-    <item msgid="492518447401534767">"Conectada"</item>
+    <item msgid="492518447401534767">"Conectado"</item>
     <item msgid="4322989558568666518">"Suspendida"</item>
     <item msgid="286164147080824297">"Desconectando..."</item>
-    <item msgid="1628460745311787730">"Desconectada"</item>
+    <item msgid="1628460745311787730">"Desconectado"</item>
     <item msgid="4013828218541488949">"Incorrecta"</item>
     <item msgid="5473541238056528086">"Bloqueada"</item>
     <item msgid="1114588261171522603">"Evitando conexión deficiente temporalmente"</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cd847f7..20b70aa 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8030,10 +8030,10 @@
     <string name="spatial_audio_multi_toggle_off">Off</string>
 
     <!-- Bluetooth device details: spatial audio is on. [CHAR LIMIT=20]-->
-    <string name="spatial_audio_multi_toggle_on">Off</string>
+    <string name="spatial_audio_multi_toggle_on">Fixed</string>
 
     <!-- Bluetooth device details: head tracking is on. [CHAR LIMIT=20]-->
-    <string name="spatial_audio_multi_toggle_head_tracking_on">Off</string>
+    <string name="spatial_audio_multi_toggle_head_tracking_on">Head Tracking</string>
 
     <!-- Zen Modes: Summary for the Do not Disturb option that describes how many automatic rules (schedules) are enabled [CHAR LIMIT=NONE]-->
     <string name="zen_mode_settings_schedules_summary">
@@ -8249,8 +8249,7 @@
     <!-- Do not disturb: device effect option, not first in list [CHAR LIMIT=80] -->
     <string name="mode_dark_theme_title_secondary_list">enable dark theme</string>
     <!-- Do not disturb: device effect summary [CHAR LIMIT=NONE] -->
-    <string name="mode_dark_theme_summary">Switch the OS and apps to prefer light text on a dark
-        background, which may be easier on the eyes and confers significant battery savings on some devices</string>
+    <string name="mode_dark_theme_summary">Switch the device theme to use a dark background, which is easier on the eyes</string>
     <!-- [CHAR LIMIT=NONE] Zen mode settings: Summary for sound interruption settings -->
     <string name="mode_display_settings_summary">
         {count, plural, offset:2
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index e4ebe788..292f182 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -52,9 +52,8 @@
         android:order="-15"
         settings:keywords="@string/keywords_more_mobile_networks"
         settings:userRestriction="no_config_mobile_networks"
-        settings:isPreferenceVisible="@bool/config_show_sim_info"
         settings:useAdminDisabledSummary="true"
-        settings:searchable="@bool/config_show_sim_info"/>
+        settings:controller="com.android.settings.network.MobileNetworkSummaryController" />
 
     <com.android.settingslib.RestrictedSwitchPreference
         android:key="airplane_mode"
diff --git a/src/com/android/settings/backup/SettingsBackupHelper.java b/src/com/android/settings/backup/SettingsBackupHelper.java
index 73760a4..5fdbb00 100644
--- a/src/com/android/settings/backup/SettingsBackupHelper.java
+++ b/src/com/android/settings/backup/SettingsBackupHelper.java
@@ -16,17 +16,19 @@
 
 package com.android.settings.backup;
 
-
 import android.app.backup.BackupAgentHelper;
+import android.util.Log;
 
 import com.android.settings.flags.Flags;
 import com.android.settings.onboarding.OnboardingFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.shortcut.CreateShortcutPreferenceController;
+import com.android.settings.shortcut.ShortcutsUpdater;
 import com.android.settingslib.datastore.BackupRestoreStorageManager;
 
 /** Backup agent for Settings APK */
 public class SettingsBackupHelper extends BackupAgentHelper {
+    private static final String TAG = "SettingsBackupHelper";
+
     public static final String SOUND_BACKUP_HELPER = "SoundSettingsBackup";
     public static final String ACCESSIBILITY_APPEARANCE_BACKUP_HELPER =
             "AccessibilityAppearanceSettingsBackup";
@@ -58,6 +60,10 @@
     public void onRestoreFinished() {
         super.onRestoreFinished();
         BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
-        CreateShortcutPreferenceController.updateRestoredShortcuts(this);
+        try {
+            ShortcutsUpdater.updatePinnedShortcuts(this);
+        } catch (Exception e) {
+            Log.e(TAG, "Error updating shortcuts after restoring backup", e);
+        }
     }
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
index c798dff..c28f9e0 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java
@@ -93,6 +93,7 @@
         } else if (mShouldUseReverseLandscape) {
             swapHeaderAndContent();
         }
+        mUdfpsEnrollView.setVisibility(View.VISIBLE);
         setOnHoverListener();
     }
 
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
index d71328e..86f090e 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -18,32 +18,94 @@
 
 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
 
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
+
+import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
 
 import com.android.settings.R;
+import com.android.settings.SettingsActivity;
 import com.android.settings.accessibility.AccessibilityStatsLogUtils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingIncompatibleDialogFragment;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth
  * device pairing detail page.
  */
 public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
+    private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10);
+    private static final int AUTO_DISMISS_MESSAGE_ID = 1001;
 
     protected boolean mInitialScanStarted;
     @VisibleForTesting
     protected BluetoothProgressCategory mAvailableDevicesCategory;
+    @Nullable
+    private volatile BluetoothDevice mJustBonded = null;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+    @Nullable
+    private AlertDialog mLoadingDialog = null;
+    @VisibleForTesting
+    boolean mShouldTriggerAudioSharingShareThenPairFlow = false;
+    private CopyOnWriteArrayList<BluetoothDevice> mDevicesWithMetadataChangedListener =
+            new CopyOnWriteArrayList<>();
+
+    // BluetoothDevicePreference updates the summary based on several callbacks, including
+    // BluetoothAdapter.OnMetadataChangedListener and BluetoothCallback. In most cases,
+    // metadata changes callback will be triggered before onDeviceBondStateChanged(BOND_BONDED).
+    // And before we hear onDeviceBondStateChanged(BOND_BONDED), the BluetoothDevice.getState() has
+    // already been BOND_BONDED. These event sequence will lead to: before we hear
+    // onDeviceBondStateChanged(BOND_BONDED), BluetoothDevicePreference's summary has already
+    // change from "Pairing..." to empty since it listens to metadata changes happens earlier.
+    //
+    // In share then pair flow, we have to wait on this page till the device is connected.
+    // The BluetoothDevicePreference summary will be blank for seconds between "Pairing..." and
+    // "Connecting..." To help users better understand the process, we listen to metadata change
+    // as well and show a loading dialog with "Connecting to ...." once BluetoothDevice.getState()
+    // gets to BOND_BONDED.
+    final BluetoothAdapter.OnMetadataChangedListener mMetadataListener =
+            new BluetoothAdapter.OnMetadataChangedListener() {
+                @Override
+                public void onMetadataChanged(@NonNull BluetoothDevice device, int key,
+                        @Nullable byte[] value) {
+                    Log.d(getLogTag(), "onMetadataChanged device = " + device + ", key  = " + key);
+                    if (mShouldTriggerAudioSharingShareThenPairFlow && mLoadingDialog == null
+                            && device.getBondState() == BluetoothDevice.BOND_BONDED
+                            && mSelectedList.contains(device)) {
+                        triggerAudioSharingShareThenPairFlow(device);
+                        // Once device is bonded, remove the listener
+                        removeOnMetadataChangedListener(device);
+                    }
+                }
+            };
 
     public BluetoothDevicePairingDetailBase() {
         super(DISALLOW_CONFIG_BLUETOOTH);
@@ -68,6 +130,7 @@
             return;
         }
         updateBluetooth();
+        mShouldTriggerAudioSharingShareThenPairFlow = shouldTriggerAudioSharingShareThenPairFlow();
     }
 
     @Override
@@ -81,6 +144,26 @@
     }
 
     @Override
+    public void onDestroy() {
+        super.onDestroy();
+        var unused = ThreadUtils.postOnBackgroundThread(() -> {
+            mDevicesWithMetadataChangedListener.forEach(
+                    device -> {
+                        try {
+                            if (mBluetoothAdapter != null) {
+                                mBluetoothAdapter.removeOnMetadataChangedListener(device,
+                                        mMetadataListener);
+                                mDevicesWithMetadataChangedListener.remove(device);
+                            }
+                        } catch (IllegalArgumentException e) {
+                            Log.d(getLogTag(), "Fail to remove listener: " + e);
+                        }
+                    });
+            mDevicesWithMetadataChangedListener.clear();
+        });
+    }
+
+    @Override
     public void onBluetoothStateChanged(int bluetoothState) {
         super.onBluetoothStateChanged(bluetoothState);
         updateContent(bluetoothState);
@@ -92,16 +175,37 @@
     @Override
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
         if (bondState == BluetoothDevice.BOND_BONDED) {
+            if (cachedDevice != null && mShouldTriggerAudioSharingShareThenPairFlow) {
+                BluetoothDevice device = cachedDevice.getDevice();
+                if (device != null && mSelectedList.contains(device)) {
+                    triggerAudioSharingShareThenPairFlow(device);
+                    removeOnMetadataChangedListener(device);
+                    return;
+                }
+            }
             // If one device is connected(bonded), then close this fragment.
             finish();
             return;
         } else if (bondState == BluetoothDevice.BOND_BONDING) {
+            if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) {
+                BluetoothDevice device = cachedDevice.getDevice();
+                if (device != null && mSelectedList.contains(device)) {
+                    addOnMetadataChangedListener(device);
+                }
+            }
             // Set the bond entry where binding process starts for logging hearing aid device info
             final int pageId = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
                     .getAttribution(getActivity());
             final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
                     pageId);
             HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
+        } else if (bondState == BluetoothDevice.BOND_NONE) {
+            if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) {
+                BluetoothDevice device = cachedDevice.getDevice();
+                if (device != null && mSelectedList.contains(device)) {
+                    removeOnMetadataChangedListener(device);
+                }
+            }
         }
         if (mSelectedDevice != null && cachedDevice != null) {
             BluetoothDevice device = cachedDevice.getDevice();
@@ -114,7 +218,8 @@
     }
 
     @Override
-    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
+    public void onProfileConnectionStateChanged(
+            @NonNull CachedBluetoothDevice cachedDevice, @ConnectionState int state,
             int bluetoothProfile) {
         // This callback is used to handle the case that bonded device is connected in pairing list.
         // 1. If user selected multiple bonded devices in pairing list, after connected
@@ -123,8 +228,22 @@
         // removed from paring list.
         if (cachedDevice != null && cachedDevice.isConnected()) {
             final BluetoothDevice device = cachedDevice.getDevice();
-            if (device != null && mSelectedList.contains(device)) {
-                finish();
+            if (device != null
+                    && mSelectedList.contains(device)) {
+                if (!BluetoothUtils.isAudioSharingEnabled()) {
+                    finish();
+                    return;
+                }
+                if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+                        && state == BluetoothAdapter.STATE_CONNECTED
+                        && device.equals(mJustBonded)
+                        && mShouldTriggerAudioSharingShareThenPairFlow) {
+                    Log.d(getLogTag(),
+                            "onProfileConnectionStateChanged, assistant profile connected");
+                    dismissConnectingDialog();
+                    mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID);
+                    finishFragmentWithResultForAudioSharing(device);
+                }
             } else {
                 onDeviceDeleted(cachedDevice);
             }
@@ -148,6 +267,8 @@
     public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
         disableScanning();
         super.onDevicePreferenceClick(btPreference);
+        // Clean up the previous bond value
+        mJustBonded = null;
     }
 
     @VisibleForTesting
@@ -165,8 +286,8 @@
      * {@code bluetoothState} is off.
      *
      * @param bluetoothState the current Bluetooth state, the possible values that will handle here:
-     * {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
-     * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
+     *                       {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
+     *                       {@link android.bluetooth.BluetoothAdapter#STATE_ON},
      */
     @VisibleForTesting
     public void updateContent(int bluetoothState) {
@@ -187,4 +308,122 @@
         Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
                 Toast.LENGTH_SHORT).show();
     }
+
+    @VisibleForTesting
+    boolean shouldTriggerAudioSharingShareThenPairFlow() {
+        if (!BluetoothUtils.isAudioSharingEnabled()) return false;
+        Activity activity = getActivity();
+        Intent intent = activity == null ? null : activity.getIntent();
+        Bundle args =
+                intent == null ? null :
+                        intent.getBundleExtra(
+                                SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+        return args != null
+                && args.getBoolean(EXTRA_PAIR_AND_JOIN_SHARING, false);
+    }
+
+    private void addOnMetadataChangedListener(@Nullable BluetoothDevice device) {
+        var unused = ThreadUtils.postOnBackgroundThread(() -> {
+            if (mBluetoothAdapter != null && device != null
+                    && !mDevicesWithMetadataChangedListener.contains(device)) {
+                mBluetoothAdapter.addOnMetadataChangedListener(device, mExecutor,
+                        mMetadataListener);
+                mDevicesWithMetadataChangedListener.add(device);
+            }
+        });
+    }
+
+    private void removeOnMetadataChangedListener(@Nullable BluetoothDevice device) {
+        var unused = ThreadUtils.postOnBackgroundThread(() -> {
+            if (mBluetoothAdapter != null && device != null
+                    && mDevicesWithMetadataChangedListener.contains(device)) {
+                try {
+                    mBluetoothAdapter.removeOnMetadataChangedListener(device, mMetadataListener);
+                    mDevicesWithMetadataChangedListener.remove(device);
+                } catch (IllegalArgumentException e) {
+                    Log.d(getLogTag(), "Fail to remove listener: " + e);
+                }
+            }
+        });
+    }
+
+    private void triggerAudioSharingShareThenPairFlow(
+            @NonNull BluetoothDevice device) {
+        var unused = ThreadUtils.postOnBackgroundThread(() -> {
+            if (mJustBonded != null) {
+                Log.d(getLogTag(), "Skip triggerAudioSharingShareThenPairFlow, already done");
+                return;
+            }
+            mJustBonded = device;
+            // Show connecting device loading state
+            String aliasName = device.getAlias();
+            String deviceName = TextUtils.isEmpty(aliasName) ? device.getAddress()
+                    : aliasName;
+            showConnectingDialog("Connecting to " + deviceName + "...");
+            // Wait for AUTO_DISMISS_TIME_THRESHOLD_MS and check if the paired device supports audio
+            // sharing.
+            if (!mHandler.hasMessages(AUTO_DISMISS_MESSAGE_ID)) {
+                mHandler.postDelayed(() ->
+                        postOnMainThread(
+                                () -> {
+                                    Log.d(getLogTag(), "Show incompatible dialog when timeout");
+                                    dismissConnectingDialog();
+                                    AudioSharingIncompatibleDialogFragment.show(this, deviceName,
+                                            () -> finish());
+                                }), AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
+            }
+        });
+    }
+
+    private void finishFragmentWithResultForAudioSharing(@Nullable BluetoothDevice device) {
+        Intent resultIntent = new Intent();
+        resultIntent.putExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
+        if (getActivity() != null) {
+            getActivity().setResult(Activity.RESULT_OK, resultIntent);
+        }
+        finish();
+    }
+
+    // TODO: use DialogFragment
+    private void showConnectingDialog(@NonNull String message) {
+        postOnMainThread(() -> {
+            if (mLoadingDialog != null) {
+                Log.d(getLogTag(), "showConnectingDialog, is already showing");
+                TextView textView = mLoadingDialog.findViewById(R.id.message);
+                if (textView != null && !message.equals(textView.getText().toString())) {
+                    Log.d(getLogTag(), "showConnectingDialog, update message");
+                    // TODO: use string res once finalized
+                    textView.setText(message);
+                }
+                return;
+            }
+            Log.d(getLogTag(), "showConnectingDialog, show dialog");
+            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+            LayoutInflater inflater = LayoutInflater.from(builder.getContext());
+            View customView = inflater.inflate(
+                    R.layout.dialog_audio_sharing_loading_state, /* root= */
+                    null);
+            TextView textView = customView.findViewById(R.id.message);
+            if (textView != null) {
+                // TODO: use string res once finalized
+                textView.setText(message);
+            }
+            AlertDialog dialog = builder.setView(customView).setCancelable(false).create();
+            dialog.setCanceledOnTouchOutside(false);
+            mLoadingDialog = dialog;
+            dialog.show();
+        });
+    }
+
+    private void dismissConnectingDialog() {
+        postOnMainThread(() -> {
+            if (mLoadingDialog != null) {
+                mLoadingDialog.dismiss();
+            }
+        });
+    }
+
+    private void postOnMainThread(@NonNull Runnable runnable) {
+        getContext().getMainExecutor().execute(runnable);
+    }
 }
diff --git a/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
index d29795e..9743737 100644
--- a/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
+++ b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -62,6 +63,7 @@
 import androidx.compose.ui.semantics.toggleableState
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.window.DialogProperties
@@ -78,7 +80,11 @@
     var settingIdForPopUp by remember { mutableStateOf<Int?>(null) }
 
     settingIdForPopUp?.let { id ->
-        preferenceModels.find { it.id == id }?.let { dialog(it) { settingIdForPopUp = null } }
+        preferenceModels.find { it.id == id && it.isAllowedChangingState }?.let {
+            dialog(it) { settingIdForPopUp = null }
+        } ?: run {
+            settingIdForPopUp = null
+        }
     }
 
     Row(
@@ -102,7 +108,9 @@
                                     Modifier.fillMaxSize().padding(8.dp).semantics {
                                         role = Role.Switch
                                         toggleableState =
-                                            if (preferenceModel.isActive) {
+                                            if (!preferenceModel.isAllowedChangingState) {
+                                                ToggleableState.Indeterminate
+                                            } else if (preferenceModel.isActive) {
                                                 ToggleableState.On
                                             } else {
                                                 ToggleableState.Off
@@ -110,6 +118,7 @@
                                         contentDescription = preferenceModel.title
                                     },
                                 onClick = { settingIdForPopUp = preferenceModel.id },
+                                enabled = preferenceModel.isAllowedChangingState,
                                 shape = RoundedCornerShape(20.dp),
                                 colors = getButtonColors(preferenceModel.isActive),
                                 contentPadding = PaddingValues(0.dp)) {
@@ -254,7 +263,7 @@
         }
         Spacer(modifier = Modifier.height(12.dp))
         Row(
-            modifier = Modifier.fillMaxWidth().height(32.dp),
+            modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp),
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.SpaceEvenly,
         ) {
@@ -263,6 +272,7 @@
                     text = toggle.label,
                     fontSize = 12.sp,
                     textAlign = TextAlign.Center,
+                    overflow = TextOverflow.Visible,
                     modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
             }
         }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index ad41e8a..786e1dc 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -16,10 +16,18 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.Bundle;
 
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.R;
@@ -27,16 +35,21 @@
 import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.utils.ThreadUtils;
 
 public class AudioSharingDashboardFragment extends DashboardFragment
         implements AudioSharingSwitchBarController.OnAudioSharingStateChangedListener {
     private static final String TAG = "AudioSharingDashboardFrag";
 
+    public static final int SHARE_THEN_PAIR_REQUEST_CODE = 1002;
+
     SettingsMainSwitchBar mMainSwitchBar;
     private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
     private AudioSharingCallAudioPreferenceController mAudioSharingCallAudioPreferenceController;
     private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
     private AudioStreamsCategoryController mAudioStreamsCategoryController;
+    private AudioSharingSwitchBarController mAudioSharingSwitchBarController;
 
     public AudioSharingDashboardFragment() {
         super();
@@ -84,14 +97,39 @@
         final SettingsActivity activity = (SettingsActivity) getActivity();
         mMainSwitchBar = activity.getSwitchBar();
         mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
-        AudioSharingSwitchBarController switchBarController =
+        mAudioSharingSwitchBarController =
                 new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
-        switchBarController.init(this);
-        getSettingsLifecycle().addObserver(switchBarController);
+        mAudioSharingSwitchBarController.init(this);
+        getSettingsLifecycle().addObserver(mAudioSharingSwitchBarController);
         mMainSwitchBar.show();
     }
 
     @Override
+    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (!BluetoothUtils.isAudioSharingEnabled()) return;
+        // In share then pair flow, after users be routed to pair new device page and successfully
+        // pair and connect an LEA headset, the pair fragment will be finished with RESULT_OK
+        // and EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, pass the BT device to switch bar controller,
+        // which is responsible for adding source to the device with loading indicator.
+        if (requestCode == SHARE_THEN_PAIR_REQUEST_CODE) {
+            if (resultCode == Activity.RESULT_OK) {
+                BluetoothDevice btDevice =
+                        data != null
+                                ? data.getParcelableExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE,
+                                BluetoothDevice.class)
+                                : null;
+                Log.d(TAG, "onActivityResult: RESULT_OK with device = " + btDevice);
+                if (btDevice != null) {
+                    var unused = ThreadUtils.postOnBackgroundThread(
+                            () -> mAudioSharingSwitchBarController.handleAutoAddSourceAfterPair(
+                                    btDevice));
+                }
+            }
+        }
+    }
+
+    @Override
     public void onAudioSharingStateChanged() {
         updateVisibilityForAttachedPreferences();
     }
@@ -107,11 +145,13 @@
             AudioSharingDeviceVolumeGroupController volumeGroupController,
             AudioSharingCallAudioPreferenceController callAudioController,
             AudioSharingPlaySoundPreferenceController playSoundController,
-            AudioStreamsCategoryController streamsCategoryController) {
+            AudioStreamsCategoryController streamsCategoryController,
+            AudioSharingSwitchBarController switchBarController) {
         mAudioSharingDeviceVolumeGroupController = volumeGroupController;
         mAudioSharingCallAudioPreferenceController = callAudioController;
         mAudioSharingPlaySoundPreferenceController = playSoundController;
         mAudioStreamsCategoryController = streamsCategoryController;
+        mAudioSharingSwitchBarController = switchBarController;
     }
 
     private void updateVisibilityForAttachedPreferences() {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index 1ae541c..1b68eac 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
+
 import android.app.Dialog;
 import android.app.settings.SettingsEnums;
 import android.os.Bundle;
@@ -48,19 +51,23 @@
     // The host creates an instance of this dialog fragment must implement this interface to receive
     // event callbacks.
     public interface DialogEventListener {
+        /** Called when users click the positive button in the dialog. */
+        default void onPositiveClick() {}
+
         /**
          * Called when users click the device item for sharing in the dialog.
          *
          * @param item The device item clicked.
          */
-        void onItemClick(AudioSharingDeviceItem item);
+        default void onItemClick(@NonNull AudioSharingDeviceItem item) {}
 
         /** Called when users click the cancel button in the dialog. */
-        void onCancelClick();
+        default void onCancelClick() {}
     }
 
     @Nullable private static DialogEventListener sListener;
     private static Pair<Integer, Object>[] sEventData = new Pair[0];
+    @Nullable private static Fragment sHost;
 
     @Override
     public int getMetricsCategory() {
@@ -70,10 +77,10 @@
     /**
      * Display the {@link AudioSharingDialogFragment} dialog.
      *
-     * @param host The Fragment this dialog will be hosted.
+     * @param host        The Fragment this dialog will be hosted.
      * @param deviceItems The connected device items eligible for audio sharing.
-     * @param listener The callback to handle the user action on this dialog.
-     * @param eventData The eventData to log with for dialog onClick events.
+     * @param listener    The callback to handle the user action on this dialog.
+     * @param eventData   The eventData to log with for dialog onClick events.
      */
     public static void show(
             @NonNull Fragment host,
@@ -88,6 +95,7 @@
             Log.d(TAG, "Fail to show dialog: " + e.getMessage());
             return;
         }
+        sHost = host;
         sListener = listener;
         sEventData = eventData;
         AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG);
@@ -136,23 +144,33 @@
                     .setCustomPositiveButton(
                             R.string.audio_sharing_pair_button_label,
                             v -> {
-                                dismiss();
-                                new SubSettingLauncher(getContext())
-                                        .setDestination(BluetoothPairingDetail.class.getName())
-                                        .setSourceMetricsCategory(getMetricsCategory())
-                                        .launch();
+                                if (sListener != null) {
+                                    sListener.onPositiveClick();
+                                }
                                 logDialogPositiveBtnClick();
+                                dismiss();
+                                Bundle args = new Bundle();
+                                args.putBoolean(EXTRA_PAIR_AND_JOIN_SHARING, true);
+                                SubSettingLauncher launcher =
+                                        new SubSettingLauncher(getContext())
+                                                .setDestination(
+                                                        BluetoothPairingDetail.class.getName())
+                                                .setSourceMetricsCategory(getMetricsCategory())
+                                                .setArguments(args);
+                                if (sHost != null) {
+                                    launcher.setResultListener(sHost, SHARE_THEN_PAIR_REQUEST_CODE);
+                                }
+                                launcher.launch();
                             })
                     .setCustomNegativeButton(
                             R.string.audio_sharing_qrcode_button_label,
                             v -> {
-                                dismiss();
+                                onCancelClick();
                                 new SubSettingLauncher(getContext())
                                         .setTitleRes(R.string.audio_streams_qr_code_page_title)
                                         .setDestination(AudioStreamsQrCodeFragment.class.getName())
                                         .setSourceMetricsCategory(getMetricsCategory())
                                         .launch();
-                                logDialogNegativeBtnClick();
                             });
         } else if (deviceItems.size() == 1) {
             AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
@@ -166,8 +184,8 @@
                             v -> {
                                 if (sListener != null) {
                                     sListener.onItemClick(deviceItem);
-                                    logDialogPositiveBtnClick();
                                 }
+                                logDialogPositiveBtnClick();
                                 dismiss();
                             })
                     .setCustomNegativeButton(
@@ -182,8 +200,8 @@
                                     (AudioSharingDeviceItem item) -> {
                                         if (sListener != null) {
                                             sListener.onItemClick(item);
-                                            logDialogPositiveBtnClick();
                                         }
+                                        logDialogPositiveBtnClick();
                                         dismiss();
                                     },
                                     AudioSharingDeviceAdapter.ActionType.SHARE))
@@ -196,8 +214,8 @@
     private void onCancelClick() {
         if (sListener != null) {
             sListener.onCancelClick();
-            logDialogNegativeBtnClick();
         }
+        logDialogNegativeBtnClick();
         dismiss();
     }
 
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
index 5de615e..aceeb94 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragment.java
@@ -29,7 +29,6 @@
 
 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";
@@ -59,7 +58,7 @@
      *
      * @param host The Fragment this dialog will be hosted.
      */
-    public static void show(@Nullable Fragment host, @NonNull CachedBluetoothDevice cachedDevice,
+    public static void show(@Nullable Fragment host, @NonNull String deviceName,
             @NonNull DialogEventListener listener) {
         if (host == null || !BluetoothUtils.isAudioSharingEnabled()) return;
         final FragmentManager manager;
@@ -77,7 +76,7 @@
         }
         Log.d(TAG, "Show up the incompatible device dialog.");
         final Bundle bundle = new Bundle();
-        bundle.putString(BUNDLE_KEY_DEVICE_NAME, cachedDevice.getName());
+        bundle.putString(BUNDLE_KEY_DEVICE_NAME, deviceName);
         AudioSharingIncompatibleDialogFragment dialogFrag =
                 new AudioSharingIncompatibleDialogFragment();
         dialogFrag.setArguments(bundle);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
index 79cc56e..8706590 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java
@@ -115,10 +115,6 @@
     @NonNull
     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
         mHandler = new Handler(Looper.getMainLooper());
-        mHandler.postDelayed(() -> {
-            Log.d(TAG, "Auto dismiss dialog after timeout");
-            dismiss();
-        }, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
         Bundle args = requireArguments();
         String message = args.getString(BUNDLE_KEY_MESSAGE, "");
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -133,6 +129,26 @@
     }
 
     @Override
+    public void onStart() {
+        super.onStart();
+        if (mHandler != null) {
+            Log.d(TAG, "onStart, postTimeOut for auto dismiss");
+            mHandler.postDelayed(() -> {
+                Log.d(TAG, "Try to auto dismiss dialog after timeout");
+                try {
+                    Dialog dialog = getDialog();
+                    if (dialog != null) {
+                        Log.d(TAG, "Dialog is not null, dismiss");
+                        dismissAllowingStateLoss();
+                    }
+                } catch (IllegalStateException e) {
+                    Log.d(TAG, "Fail to dismiss: " + e.getMessage());
+                }
+            }, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS);
+        }
+    }
+
+    @Override
     public void onDismiss(@NonNull DialogInterface dialog) {
         super.onDismiss(dialog);
         if (mHandler != null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index c0f463d..395647c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -56,6 +56,7 @@
 import com.android.settingslib.bluetooth.BluetoothEventManager;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -78,9 +79,9 @@
 
 public class AudioSharingSwitchBarController extends BasePreferenceController
         implements DefaultLifecycleObserver,
-                OnCheckedChangeListener,
-                LocalBluetoothProfileManager.ServiceListener,
-                BluetoothCallback {
+        OnCheckedChangeListener,
+        LocalBluetoothProfileManager.ServiceListener,
+        BluetoothCallback {
     private static final String TAG = "AudioSharingSwitchCtlr";
     private static final String PREF_KEY = "audio_sharing_main_switch";
 
@@ -464,6 +465,18 @@
         this.mFragment = fragment;
     }
 
+    /** Handle auto add source to the just paired device in share then pair flow. */
+    public void handleAutoAddSourceAfterPair(@NonNull BluetoothDevice device) {
+        CachedBluetoothDeviceManager deviceManager =
+                mBtManager == null ? null : mBtManager.getCachedDeviceManager();
+        CachedBluetoothDevice cachedDevice =
+                deviceManager == null ? null : deviceManager.findDevice(device);
+        if (cachedDevice != null) {
+            Log.d(TAG, "handleAutoAddSourceAfterPair, device = " + device.getAnonymizedAddress());
+            addSourceToTargetSinks(ImmutableList.of(device), cachedDevice.getName());
+        }
+    }
+
     /** Test only: set callback registration status in tests. */
     @VisibleForTesting
     void setCallbacksRegistered(boolean registered) {
@@ -610,8 +623,8 @@
             mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING);
             mTargetActiveItem = null;
             if (mIntentHandleStage.compareAndSet(
-                            StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
-                            StartIntentHandleStage.HANDLED.ordinal())
+                    StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(),
+                    StartIntentHandleStage.HANDLED.ordinal())
                     && mDeviceItemsForSharing.size() == 1) {
                 Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device");
                 AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0);
@@ -639,6 +652,13 @@
         AudioSharingDialogFragment.DialogEventListener listener =
                 new AudioSharingDialogFragment.DialogEventListener() {
                     @Override
+                    public void onPositiveClick() {
+                        // Could go to other pages, dismiss the loading dialog.
+                        dismissLoadingStateDialogIfNeeded();
+                        cleanUp();
+                    }
+
+                    @Override
                     public void onItemClick(@NonNull AudioSharingDeviceItem item) {
                         List<BluetoothDevice> targetSinks = mGroupedConnectedDevices.getOrDefault(
                                 item.getGroupId(), ImmutableList.of());
@@ -648,6 +668,7 @@
 
                     @Override
                     public void onCancelClick() {
+                        // Could go to other pages, dismiss the loading dialog.
                         dismissLoadingStateDialogIfNeeded();
                         cleanUp();
                     }
@@ -669,8 +690,8 @@
                 @NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) {
             if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
                     && (event.getContentChangeTypes()
-                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
-                            != 0) {
+                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED)
+                    != 0) {
                 Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED");
                 return false;
             }
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt
new file mode 100644
index 0000000..58fdefe
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionScreen.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.deviceinfo.firmwareversion
+
+import android.content.Context
+import android.os.Build
+import com.android.settings.R
+import com.android.settings.flags.Flags
+import com.android.settingslib.metadata.PreferenceMetadata
+import com.android.settingslib.metadata.PreferenceSummaryProvider
+import com.android.settingslib.metadata.ProvidePreferenceScreen
+import com.android.settingslib.metadata.preferenceHierarchy
+import com.android.settingslib.preference.PreferenceScreenCreator
+
+@ProvidePreferenceScreen
+class FirmwareVersionScreen : PreferenceScreenCreator, PreferenceSummaryProvider {
+
+    override fun isFlagEnabled(context: Context) = Flags.catalystFirmwareVersion()
+
+    override val key: String
+        get() = KEY
+
+    override val title: Int
+        get() = R.string.firmware_version
+
+    override fun getSummary(context: Context): CharSequence? =
+        Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY
+
+    override val keywords: Int
+        get() = R.string.keywords_android_version
+
+    override fun fragmentClass() = FirmwareVersionSettings::class.java
+
+    override fun getPreferenceHierarchy(context: Context) =
+        preferenceHierarchy(this) {
+            +PreferenceWidget("os_firmware_version", R.string.firmware_version)
+            +PreferenceWidget("security_key", R.string.security_patch)
+            +PreferenceWidget("module_version", R.string.module_version)
+            +PreferenceWidget("base_band", R.string.baseband_version)
+            +PreferenceWidget("kernel_version", R.string.kernel_version)
+            +PreferenceWidget("os_build_number", R.string.build_number)
+        }
+
+    private class PreferenceWidget(override val key: String, override val title: Int) :
+        PreferenceMetadata
+
+    companion object {
+        const val KEY = "firmware_version"
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java
index e2d3d8a..d447c98 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/FirmwareVersionSettings.java
@@ -17,6 +17,10 @@
 package com.android.settings.deviceinfo.firmwareversion;
 
 import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -27,6 +31,11 @@
 public class FirmwareVersionSettings extends DashboardFragment {
 
     @Override
+    public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
+        return FirmwareVersionScreen.KEY;
+    }
+
+    @Override
     protected int getPreferenceScreenResId() {
         return R.xml.firmware_version;
     }
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index ef129ff..907fe7b 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.localepicker;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -41,7 +43,8 @@
 import com.android.internal.app.LocaleStore;
 import com.android.settings.R;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.shortcut.ShortcutsUpdateTask;
+import com.android.settings.shortcut.ShortcutsUpdater;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.text.NumberFormat;
 import java.util.ArrayList;
@@ -96,7 +99,7 @@
     LocaleDragAndDropAdapter(LocaleListEditor parent, List<LocaleStore.LocaleInfo> feedItemList) {
         mFeedItemList = feedItemList;
         mCacheItemList = new ArrayList<>(feedItemList);
-        mContext = parent.getContext();
+        mContext = checkNotNull(parent.getContext());
 
         final float dragElevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
                 mContext.getResources().getDisplayMetrics());
@@ -350,7 +353,8 @@
 
                 LocalePicker.updateLocales(mLocalesToSetNext);
                 mLocalesSetLast = mLocalesToSetNext;
-                new ShortcutsUpdateTask(mContext).execute();
+                ThreadUtils.postOnBackgroundThread(
+                        () -> ShortcutsUpdater.updatePinnedShortcuts(mContext));
 
                 mLocalesToSetNext = null;
 
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java
deleted file mode 100644
index 45d475f..0000000
--- a/src/com/android/settings/network/MobileNetworkSummaryController.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-
-import android.content.Context;
-import android.content.Intent;
-import android.telephony.SubscriptionManager;
-import android.telephony.euicc.EuiccManager;
-
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.OnLifecycleEvent;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.network.telephony.SimRepository;
-import com.android.settings.network.telephony.euicc.EuiccRepository;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
-import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
-import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class MobileNetworkSummaryController extends AbstractPreferenceController implements
-        LifecycleObserver, PreferenceControllerMixin,
-        MobileNetworkRepository.MobileNetworkCallback {
-    private static final String TAG = "MobileNetSummaryCtlr";
-
-    private static final String KEY = "mobile_network_list";
-
-    private final MetricsFeatureProvider mMetricsFeatureProvider;
-    private RestrictedPreference mPreference;
-
-    private MobileNetworkRepository mMobileNetworkRepository;
-    private List<SubscriptionInfoEntity> mSubInfoEntityList;
-    private List<UiccInfoEntity> mUiccInfoEntityList;
-    private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList;
-    private boolean mIsAirplaneModeOn;
-    private LifecycleOwner mLifecycleOwner;
-
-    /**
-     * This controls the summary text and click behavior of the "Mobile network" item on the
-     * Network & internet page. There are 3 separate cases depending on the number of mobile network
-     * subscriptions:
-     * <ul>
-     * <li>No subscription: click action begins a UI flow to add a network subscription, and
-     * the summary text indicates this</li>
-     *
-     * <li>One subscription: click action takes you to details for that one network, and
-     * the summary text is the network name</li>
-     *
-     * <li>More than one subscription: click action takes you to a page listing the subscriptions,
-     * and the summary text gives the count of SIMs</li>
-     * </ul>
-     */
-    public MobileNetworkSummaryController(Context context, Lifecycle lifecycle,
-            LifecycleOwner lifecycleOwner) {
-        super(context);
-        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-        mLifecycleOwner = lifecycleOwner;
-        mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
-        mIsAirplaneModeOn = mMobileNetworkRepository.isAirplaneModeOn();
-        if (lifecycle != null) {
-            lifecycle.addObserver(this);
-        }
-    }
-
-    @OnLifecycleEvent(ON_RESUME)
-    public void onResume() {
-        mMobileNetworkRepository.addRegister(mLifecycleOwner, this,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        mMobileNetworkRepository.updateEntity();
-    }
-
-    @OnLifecycleEvent(ON_PAUSE)
-    public void onPause() {
-        mMobileNetworkRepository.removeRegister(this);
-    }
-
-    @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-        mPreference = screen.findPreference(getPreferenceKey());
-    }
-
-    @Override
-    public CharSequence getSummary() {
-
-        if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || (
-                mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || (
-                mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) {
-            if (new EuiccRepository(mContext).showEuiccSettings()) {
-                return mContext.getResources().getString(
-                        R.string.mobile_network_summary_add_a_network);
-            }
-            // set empty string to override previous text for carrier when SIM available
-            return "";
-        } else if (mSubInfoEntityList.size() == 1) {
-            SubscriptionInfoEntity info = mSubInfoEntityList.get(0);
-            CharSequence displayName = info.uniqueName;
-            if (info.isEmbedded || mUiccInfoEntityList.get(0).isActive
-                    || mMobileNetworkInfoEntityList.get(0).showToggleForPhysicalSim) {
-                return displayName;
-            }
-            return mContext.getString(R.string.mobile_network_tap_to_activate, displayName);
-        } else {
-            return mSubInfoEntityList.stream()
-                    .map(SubscriptionInfoEntity::getUniqueDisplayName)
-                    .collect(Collectors.joining(", "));
-        }
-    }
-
-    private void logPreferenceClick(Preference preference) {
-        mMetricsFeatureProvider.logClickedPreference(preference,
-                preference.getExtras().getInt(DashboardFragment.CATEGORY));
-    }
-
-    private void startAddSimFlow() {
-        final Intent intent = new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION);
-        intent.setPackage(com.android.settings.Utils.PHONE_PACKAGE_NAME);
-        intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true);
-        mContext.startActivity(intent);
-    }
-
-    private void initPreference() {
-        refreshSummary(mPreference);
-        mPreference.setOnPreferenceClickListener(null);
-        mPreference.setFragment(null);
-        mPreference.setEnabled(!mIsAirplaneModeOn);
-    }
-
-    private void update() {
-        if (mPreference == null || mPreference.isDisabledByAdmin()) {
-            return;
-        }
-
-        initPreference();
-        if (((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty())
-                || (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty())
-                || (mMobileNetworkInfoEntityList == null
-                || mMobileNetworkInfoEntityList.isEmpty()))) {
-            if (new EuiccRepository(mContext).showEuiccSettings()) {
-                mPreference.setOnPreferenceClickListener((Preference pref) -> {
-                    logPreferenceClick(pref);
-                    startAddSimFlow();
-                    return true;
-                });
-            } else {
-                mPreference.setEnabled(false);
-            }
-            return;
-        }
-
-        mPreference.setFragment(MobileNetworkListFragment.class.getCanonicalName());
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return new SimRepository(mContext).showMobileNetworkPage();
-    }
-
-    @Override
-    public String getPreferenceKey() {
-        return KEY;
-    }
-
-    @Override
-    public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
-        if (mIsAirplaneModeOn != airplaneModeEnabled) {
-            mIsAirplaneModeOn = airplaneModeEnabled;
-            update();
-        }
-    }
-
-    @Override
-    public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
-        mSubInfoEntityList = subInfoEntityList;
-        update();
-    }
-
-    @Override
-    public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
-        mUiccInfoEntityList = uiccInfoEntityList;
-        update();
-    }
-
-    @Override
-    public void onAllMobileNetworkInfoChanged(
-            List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
-        mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
-        update();
-    }
-}
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.kt b/src/com/android/settings/network/MobileNetworkSummaryController.kt
new file mode 100644
index 0000000..5980bbd
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkSummaryController.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.content.Context
+import android.provider.Settings
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.network.telephony.SimRepository
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
+import com.android.settings.spa.network.startAddSimFlow
+import com.android.settingslib.RestrictedPreference
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * This controls the summary text and click behavior of the "Mobile network" item on the Network &
+ * internet page. There are 2 separate cases depending on the number of mobile network
+ * subscriptions:
+ * - No subscription: click action begins a UI flow to add a network subscription, and the summary
+ *   text indicates this
+ * - Has subscriptions: click action takes you to a page listing the subscriptions, and the summary
+ *   text gives the count of SIMs
+ */
+class MobileNetworkSummaryController
+@JvmOverloads
+constructor(
+    private val context: Context,
+    preferenceKey: String,
+    private val repository: MobileNetworkSummaryRepository =
+        MobileNetworkSummaryRepository(context),
+    private val airplaneModeOnFlow: Flow<Boolean> =
+        context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
+) : BasePreferenceController(context, preferenceKey) {
+    private val metricsFeatureProvider = featureFactory.metricsFeatureProvider
+    private var preference: RestrictedPreference? = null
+
+    private var isAirplaneModeOn = false
+
+    override fun getAvailabilityStatus() =
+        if (SimRepository(mContext).showMobileNetworkPage()) AVAILABLE
+        else CONDITIONALLY_UNAVAILABLE
+
+    override fun displayPreference(screen: PreferenceScreen) {
+        super.displayPreference(screen)
+        preference = screen.findPreference(preferenceKey)
+    }
+
+    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+        repository
+            .subscriptionsStateFlow()
+            .collectLatestWithLifecycle(viewLifecycleOwner, action = ::update)
+        airplaneModeOnFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
+            isAirplaneModeOn = it
+            updateEnabled()
+        }
+    }
+
+    private fun update(state: MobileNetworkSummaryRepository.SubscriptionsState) {
+        val preference = preference ?: return
+        preference.onPreferenceClickListener = null
+        preference.fragment = null
+        when (state) {
+            MobileNetworkSummaryRepository.AddNetwork -> {
+                preference.summary =
+                    context.getString(R.string.mobile_network_summary_add_a_network)
+                preference.onPreferenceClickListener =
+                    Preference.OnPreferenceClickListener {
+                        logPreferenceClick()
+                        startAddSimFlow(context)
+                        true
+                    }
+            }
+
+            MobileNetworkSummaryRepository.NoSubscriptions -> {
+                preference.summary = null
+            }
+
+            is MobileNetworkSummaryRepository.HasSubscriptions -> {
+                preference.summary = state.displayNames.joinToString(", ")
+                preference.fragment = MobileNetworkListFragment::class.java.canonicalName
+            }
+        }
+        updateEnabled()
+    }
+
+    private fun updateEnabled() {
+        val preference = preference ?: return
+        if (preference.isDisabledByAdmin) return
+        preference.isEnabled =
+            (preference.onPreferenceClickListener != null || preference.fragment != null) &&
+                !isAirplaneModeOn
+    }
+
+    private fun logPreferenceClick() {
+        val preference = preference ?: return
+        metricsFeatureProvider.logClickedPreference(
+            preference,
+            preference.extras.getInt(DashboardFragment.CATEGORY),
+        )
+    }
+}
diff --git a/src/com/android/settings/network/MobileNetworkSummaryRepository.kt b/src/com/android/settings/network/MobileNetworkSummaryRepository.kt
new file mode 100644
index 0000000..edf557b
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkSummaryRepository.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.content.Context
+import android.telephony.SubscriptionInfo
+import com.android.settings.network.telephony.SubscriptionRepository
+import com.android.settings.network.telephony.euicc.EuiccRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+class MobileNetworkSummaryRepository(
+    private val context: Context,
+    private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
+    private val euiccRepository: EuiccRepository = EuiccRepository(context),
+    private val getDisplayName: (SubscriptionInfo) -> String = { subInfo ->
+        SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context).toString()
+    },
+) {
+    sealed interface SubscriptionsState
+
+    data object AddNetwork : SubscriptionsState
+
+    data object NoSubscriptions : SubscriptionsState
+
+    data class HasSubscriptions(val displayNames: List<String>) : SubscriptionsState
+
+    fun subscriptionsStateFlow(): Flow<SubscriptionsState> =
+        subDisplayNamesFlow()
+            .map { displayNames ->
+                if (displayNames.isEmpty()) {
+                    if (euiccRepository.showEuiccSettings()) AddNetwork else NoSubscriptions
+                } else {
+                    HasSubscriptions(displayNames)
+                }
+            }
+            .distinctUntilChanged()
+            .conflate()
+            .flowOn(Dispatchers.Default)
+
+    private fun subDisplayNamesFlow(): Flow<List<String>> =
+        subscriptionRepository
+            .selectableSubscriptionInfoListFlow()
+            .map { subInfos -> subInfos.map(getDisplayName) }
+            .distinctUntilChanged()
+            .conflate()
+            .flowOn(Dispatchers.Default)
+}
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index aff9130..ee7d440 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 
-import androidx.lifecycle.LifecycleOwner;
+import androidx.annotation.Nullable;
 
 import com.android.settings.R;
 import com.android.settings.SettingsDumpService;
@@ -69,12 +69,11 @@
 
     @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        return buildPreferenceControllers(context, getSettingsLifecycle(),
-                this /* LifecycleOwner */);
+        return buildPreferenceControllers(context, getSettingsLifecycle());
     }
 
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
-            Lifecycle lifecycle, LifecycleOwner lifecycleOwner) {
+            @Nullable Lifecycle lifecycle) {
         final VpnPreferenceController vpnPreferenceController =
                 new VpnPreferenceController(context);
         final PrivateDnsPreferenceController privateDnsPreferenceController =
@@ -87,7 +86,6 @@
 
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
 
-        controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
         controllers.add(vpnPreferenceController);
         controllers.add(privateDnsPreferenceController);
 
@@ -114,8 +112,7 @@
                 @Override
                 public List<AbstractPreferenceController> createPreferenceControllers(Context
                         context) {
-                    return buildPreferenceControllers(context, null /* lifecycle */,
-                            null /* LifecycleOwner */);
+                    return buildPreferenceControllers(context, null /* lifecycle */);
                 }
             };
 }
diff --git a/src/com/android/settings/network/telephony/VoNrRepository.kt b/src/com/android/settings/network/telephony/VoNrRepository.kt
index 635c572..7f3823b 100644
--- a/src/com/android/settings/network/telephony/VoNrRepository.kt
+++ b/src/com/android/settings/network/telephony/VoNrRepository.kt
@@ -19,6 +19,7 @@
 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
@@ -37,12 +38,13 @@
     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),
+                    getBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL),
                 )
             }
         return carrierConfig.isVoNrEnabled && carrierConfig.isVoNrSettingVisibility
@@ -52,7 +54,14 @@
         val telephonyManager = context.telephonyManager(subId)
         return context
             .subscriptionsChangedFlow()
-            .map { telephonyManager.isVoNrEnabled }
+            .map {
+                try {
+                    telephonyManager.isVoNrEnabled
+                } catch (e: IllegalStateException) {
+                    Log.e(TAG, "IllegalStateException - isVoNrEnabled : $e")
+                    false
+                }
+            }
             .conflate()
             .onEach { Log.d(TAG, "[$subId] isVoNrEnabled: $it") }
             .flowOn(Dispatchers.Default)
@@ -61,11 +70,17 @@
     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")
+            var result = TelephonyManager.ENABLE_VONR_RADIO_INVALID_STATE
+            try {
+                result = context.telephonyManager(subId).setVoNrEnabled(enabled)
+            } catch (e: IllegalStateException) {
+                Log.e(TAG, "IllegalStateException - setVoNrEnabled : $e")
+            } finally {
+                Log.d(TAG, "[$subId] setVoNrEnabled: $enabled, result: $result")
+            }
         }
 
     private companion object {
         private const val TAG = "VoNrRepository"
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/NotificationRingtonePreferenceController.java b/src/com/android/settings/notification/NotificationRingtonePreferenceController.java
index 00f478f..946b783 100644
--- a/src/com/android/settings/notification/NotificationRingtonePreferenceController.java
+++ b/src/com/android/settings/notification/NotificationRingtonePreferenceController.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.media.RingtoneManager;
 
+import com.android.server.notification.Flags;
 import com.android.settings.R;
 
 public class NotificationRingtonePreferenceController extends RingtonePreferenceControllerBase {
@@ -31,6 +32,9 @@
 
     @Override
     public boolean isAvailable() {
+        if (isVibrationInSoundUriEnabled()) {
+            return false;
+        }
         return mContext.getResources().getBoolean(R.bool.config_show_notification_ringtone);
     }
 
@@ -43,4 +47,9 @@
     public int getRingtoneType() {
         return RingtoneManager.TYPE_NOTIFICATION;
     }
+
+    private boolean isVibrationInSoundUriEnabled() {
+        return Flags.notificationVibrationInSoundUri() && mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported);
+    }
 }
diff --git a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java
index 8f74bd9..0e2e6bc 100644
--- a/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java
+++ b/src/com/android/settings/shortcut/CreateShortcutPreferenceController.java
@@ -16,27 +16,19 @@
 
 package com.android.settings.shortcut;
 
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
+
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.graphics.drawable.LayerDrawable;
 import android.net.ConnectivityManager;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -48,7 +40,6 @@
 import com.android.settings.Settings.DataUsageSummaryActivity;
 import com.android.settings.Settings.TetherSettingsActivity;
 import com.android.settings.Settings.WifiTetherSettingsActivity;
-import com.android.settings.activityembedding.ActivityEmbeddingUtils;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.gestures.OneHandedSettingsUtils;
 import com.android.settings.network.SubscriptionUtil;
@@ -69,11 +60,6 @@
 
     private static final String TAG = "CreateShortcutPrefCtrl";
 
-    static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
-    static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
-            .addCategory("com.android.settings.SHORTCUT")
-            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
     private final ShortcutManager mShortcutManager;
     private final PackageManager mPackageManager;
     private final ConnectivityManager mConnectivityManager;
@@ -132,9 +118,7 @@
                 if (mHost == null) {
                     return false;
                 }
-                final Intent shortcutIntent = createResultIntent(
-                        buildShortcutIntent(uiContext, info),
-                        info, clickTarget.getTitle());
+                final Intent shortcutIntent = createResultIntent(info);
                 mHost.setResult(Activity.RESULT_OK, shortcutIntent);
                 logCreateShortcut(info);
                 mHost.finish();
@@ -149,21 +133,20 @@
      * launcher widget using this intent.
      */
     @VisibleForTesting
-    Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo,
-            CharSequence label) {
-        ShortcutInfo info = createShortcutInfo(mContext, shortcutIntent, resolveInfo, label);
+    Intent createResultIntent(ResolveInfo resolveInfo) {
+        ShortcutInfo info = Shortcuts.createShortcutInfo(mContext, resolveInfo);
         Intent intent = mShortcutManager.createShortcutResultIntent(info);
         if (intent == null) {
             intent = new Intent();
         }
         intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
                 Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings))
-                .putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
-                .putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
+                .putExtra(Intent.EXTRA_SHORTCUT_INTENT, info.getIntent())
+                .putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getShortLabel());
 
         final ActivityInfo activityInfo = resolveInfo.activityInfo;
         if (activityInfo.icon != 0) {
-            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(
+            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, Shortcuts.createIcon(
                     mContext,
                     activityInfo.applicationInfo,
                     activityInfo.icon,
@@ -239,87 +222,6 @@
                 info.activityInfo.name);
     }
 
-    private static Intent buildShortcutIntent(Context context, ResolveInfo info) {
-        Intent intent = new Intent(SHORTCUT_PROBE)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                .setClassName(info.activityInfo.packageName, info.activityInfo.name);
-        if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
-            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        }
-        return intent;
-    }
-
-    private static ShortcutInfo createShortcutInfo(Context context, Intent shortcutIntent,
-            ResolveInfo resolveInfo, CharSequence label) {
-        final ActivityInfo activityInfo = resolveInfo.activityInfo;
-
-        final Icon maskableIcon;
-        if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
-            maskableIcon = Icon.createWithAdaptiveBitmap(createIcon(
-                    context,
-                    activityInfo.applicationInfo, activityInfo.icon,
-                    R.layout.shortcut_badge_maskable,
-                    context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
-        } else {
-            maskableIcon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
-        }
-        final String shortcutId = SHORTCUT_ID_PREFIX +
-                shortcutIntent.getComponent().flattenToShortString();
-        return new ShortcutInfo.Builder(context, shortcutId)
-                .setShortLabel(label)
-                .setIntent(shortcutIntent)
-                .setIcon(maskableIcon)
-                .build();
-    }
-
-    private static Bitmap createIcon(Context context, ApplicationInfo app, int resource,
-            int layoutRes, int size) {
-        final Context themedContext = new ContextThemeWrapper(context,
-                android.R.style.Theme_Material);
-        final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null);
-        final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY);
-        view.measure(spec, spec);
-        final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
-                Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(bitmap);
-
-        Drawable iconDrawable;
-        try {
-            iconDrawable = context.getPackageManager().getResourcesForApplication(app)
-                    .getDrawable(resource, themedContext.getTheme());
-            if (iconDrawable instanceof LayerDrawable) {
-                iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
-            }
-            ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon");
-            Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
-            ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon);
-        }
-
-        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
-        view.draw(canvas);
-        return bitmap;
-    }
-
-    public static void updateRestoredShortcuts(Context context) {
-        ShortcutManager sm = context.getSystemService(ShortcutManager.class);
-        List<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-        for (ShortcutInfo si : sm.getPinnedShortcuts()) {
-            if (si.getId().startsWith(SHORTCUT_ID_PREFIX)) {
-                ResolveInfo ri = context.getPackageManager().resolveActivity(si.getIntent(), 0);
-
-                if (ri != null) {
-                    updatedShortcuts.add(createShortcutInfo(context,
-                            buildShortcutIntent(context, ri), ri, si.getShortLabel()));
-                }
-            }
-        }
-        if (!updatedShortcuts.isEmpty()) {
-            sm.updateShortcuts(updatedShortcuts);
-        }
-    }
-
     private static final Comparator<ResolveInfo> SHORTCUT_COMPARATOR =
             (i1, i2) -> i1.priority - i2.priority;
 }
diff --git a/src/com/android/settings/shortcut/Shortcuts.java b/src/com/android/settings/shortcut/Shortcuts.java
new file mode 100644
index 0000000..53544eb
--- /dev/null
+++ b/src/com/android/settings/shortcut/Shortcuts.java
@@ -0,0 +1,118 @@
+/*
+ * 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.shortcut;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.settings.R;
+import com.android.settings.activityembedding.ActivityEmbeddingUtils;
+
+class Shortcuts {
+
+    private static final String TAG = "Shortcuts";
+
+    static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
+    static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
+            .addCategory("com.android.settings.SHORTCUT")
+            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+    static ShortcutInfo createShortcutInfo(Context context, ResolveInfo target) {
+        checkArgument(target.activityInfo != null);
+        String shortcutId = SHORTCUT_ID_PREFIX
+                + target.activityInfo.getComponentName().flattenToShortString();
+
+        return createShortcutInfo(context, shortcutId, target);
+    }
+
+    static ShortcutInfo createShortcutInfo(Context context, String id, ResolveInfo target) {
+        Intent intent = new Intent(SHORTCUT_PROBE)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                .setClassName(target.activityInfo.packageName, target.activityInfo.name);
+        if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        }
+
+        CharSequence label = target.loadLabel(context.getPackageManager());
+        Icon maskableIcon = getMaskableIcon(context, target.activityInfo);
+
+        return new ShortcutInfo.Builder(context, id)
+                .setIntent(intent)
+                .setShortLabel(label)
+                .setIcon(maskableIcon)
+                .build();
+    }
+
+    private static Icon getMaskableIcon(Context context, ActivityInfo activityInfo) {
+        if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
+            return Icon.createWithAdaptiveBitmap(createIcon(
+                    context,
+                    activityInfo.applicationInfo, activityInfo.icon,
+                    R.layout.shortcut_badge_maskable,
+                    context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
+        } else {
+            return Icon.createWithResource(context, R.drawable.ic_launcher_settings);
+        }
+    }
+
+    static Bitmap createIcon(Context context, ApplicationInfo app, int resource, int layoutRes,
+            int size) {
+        final Context themedContext = new ContextThemeWrapper(context,
+                android.R.style.Theme_Material);
+        final View view = LayoutInflater.from(themedContext).inflate(layoutRes, null);
+        final int spec = View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY);
+        view.measure(spec, spec);
+        final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
+                Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+
+        Drawable iconDrawable;
+        try {
+            iconDrawable = context.getPackageManager().getResourcesForApplication(app)
+                    .getDrawable(resource, themedContext.getTheme());
+            if (iconDrawable instanceof LayerDrawable) {
+                iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
+            }
+            ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon");
+            Icon icon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
+            ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon);
+        }
+
+        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
+        view.draw(canvas);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java
new file mode 100644
index 0000000..657af5b
--- /dev/null
+++ b/src/com/android/settings/shortcut/ShortcutsUpdateReceiver.java
@@ -0,0 +1,53 @@
+/*
+ * 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.shortcut;
+
+import android.app.Flags;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+public class ShortcutsUpdateReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "ShortcutsUpdateReceiver";
+
+    @Override
+    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+        if (!Flags.modesApi() || !Flags.modesUi()) {
+            return;
+        }
+
+        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            PendingResult pendingResult = goAsync();
+
+            ThreadUtils.getBackgroundExecutor().execute(() -> {
+                try {
+                    ShortcutsUpdater.updatePinnedShortcuts(context);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error trying to update Settings shortcuts", e);
+                } finally {
+                    pendingResult.finish();
+                }
+            });
+        }
+    }
+}
diff --git a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java b/src/com/android/settings/shortcut/ShortcutsUpdateTask.java
deleted file mode 100644
index 54f7d1c..0000000
--- a/src/com/android/settings/shortcut/ShortcutsUpdateTask.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.shortcut;
-
-import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
-import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_PROBE;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-import android.os.AsyncTask;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ShortcutsUpdateTask extends AsyncTask<Void, Void, Void> {
-
-    private final Context mContext;
-
-    public ShortcutsUpdateTask(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public Void doInBackground(Void... params) {
-        ShortcutManager sm = mContext.getSystemService(ShortcutManager.class);
-        PackageManager pm = mContext.getPackageManager();
-
-        List<ShortcutInfo> updates = new ArrayList<>();
-        for (ShortcutInfo info : sm.getPinnedShortcuts()) {
-            if (!info.getId().startsWith(SHORTCUT_ID_PREFIX)) {
-                continue;
-            }
-            ComponentName cn = ComponentName.unflattenFromString(
-                    info.getId().substring(SHORTCUT_ID_PREFIX.length()));
-            ResolveInfo ri = pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0);
-            if (ri == null) {
-                continue;
-            }
-            updates.add(new ShortcutInfo.Builder(mContext, info.getId())
-                    .setShortLabel(ri.loadLabel(pm)).build());
-        }
-        if (!updates.isEmpty()) {
-            sm.updateShortcuts(updates);
-        }
-        return null;
-    }
-}
diff --git a/src/com/android/settings/shortcut/ShortcutsUpdater.java b/src/com/android/settings/shortcut/ShortcutsUpdater.java
new file mode 100644
index 0000000..90a60fd
--- /dev/null
+++ b/src/com/android/settings/shortcut/ShortcutsUpdater.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.shortcut;
+
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.app.Flags;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.Settings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShortcutsUpdater {
+
+    /**
+     * Update label, icon, and intent of pinned shortcuts to Settings subpages.
+     *
+     * <p>Should be called whenever any of those could have changed, such as after changing locale,
+     * restoring a backup from a different device, or when flags controlling available features
+     * may have flipped.
+     */
+    public static void updatePinnedShortcuts(Context context) {
+        ShortcutManager sm = checkNotNull(context.getSystemService(ShortcutManager.class));
+
+        List<ShortcutInfo> updates = new ArrayList<>();
+        for (ShortcutInfo info : sm.getPinnedShortcuts()) {
+            ResolveInfo resolvedActivity = resolveActivity(context, info);
+            if (resolvedActivity != null) {
+                // Id is preserved to update an existing shortcut, but the activity it opens might
+                // be different, according to maybeGetReplacingComponent.
+                updates.add(Shortcuts.createShortcutInfo(context, info.getId(), resolvedActivity));
+            }
+        }
+        if (!updates.isEmpty()) {
+            sm.updateShortcuts(updates);
+        }
+    }
+
+    @Nullable
+    private static ResolveInfo resolveActivity(Context context, ShortcutInfo shortcut) {
+        if (!shortcut.getId().startsWith(SHORTCUT_ID_PREFIX)) {
+            return null;
+        }
+
+        ComponentName cn = ComponentName.unflattenFromString(
+                shortcut.getId().substring(SHORTCUT_ID_PREFIX.length()));
+        if (cn == null) {
+            return null;
+        }
+
+        // Check if the componentName is obsolete and has been replaced by a different one.
+        cn = maybeGetReplacingComponent(context, cn);
+        PackageManager pm = context.getPackageManager();
+        return pm.resolveActivity(new Intent(SHORTCUT_PROBE).setComponent(cn), 0);
+    }
+
+    @NonNull
+    private static ComponentName maybeGetReplacingComponent(Context context, ComponentName cn) {
+        // ZenModeSettingsActivity is replaced by ModesSettingsActivity and will be deleted
+        // soon (so we shouldn't use ZenModeSettingsActivity.class).
+        if (Flags.modesApi() && Flags.modesUi()
+                && cn.getClassName().endsWith("Settings$ZenModeSettingsActivity")) {
+            return new ComponentName(context, Settings.ModesSettingsActivity.class);
+        }
+
+        return cn;
+    }
+}
diff --git a/src/com/android/settings/spa/network/SimsSection.kt b/src/com/android/settings/spa/network/SimsSection.kt
index 276d121..bd55b32 100644
--- a/src/com/android/settings/spa/network/SimsSection.kt
+++ b/src/com/android/settings/spa/network/SimsSection.kt
@@ -137,7 +137,7 @@
     }
 }
 
-private fun startAddSimFlow(context: Context) {
+fun startAddSimFlow(context: Context) {
     val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
     intent.setPackage(Utils.PHONE_PACKAGE_NAME)
     intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
diff --git a/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java b/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java
index 14ef483..00bd0f2 100644
--- a/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java
+++ b/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java
@@ -41,19 +41,20 @@
     private static final String WALLPAPER_FOCUS = "focus_wallpaper";
     private static final String WALLPAPER_ONLY = "wallpaper_only";
     private static final String LAUNCHED_SUW = "app_launched_suw";
-
-    private String mWallpaperLaunchExtra;
+    private static final String LAUNCH_SOURCE_SETTINGS_SEARCH = "app_launched_settings_search";
 
     @Override
     protected void addExtras(Intent intent) {
+        String wallpaperLaunchExtra =
+                getResources().getString(R.string.config_wallpaper_picker_launch_extra);;
         if (WizardManagerHelper.isAnySetupWizard(intent)) {
             intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_ONLY);
-
-            mWallpaperLaunchExtra =
-                    getResources().getString(R.string.config_wallpaper_picker_launch_extra);
-            intent.putExtra(mWallpaperLaunchExtra, LAUNCHED_SUW);
+            intent.putExtra(wallpaperLaunchExtra, LAUNCHED_SUW);
         } else {
+            // This is the case when user enter the wallpaper picker from the search result entry
+            // on the Settings app
             intent.putExtra(WALLPAPER_FLAVOR_EXTRA, WALLPAPER_FOCUS);
+            intent.putExtra(wallpaperLaunchExtra, LAUNCH_SOURCE_SETTINGS_SEARCH);
         }
     }
 
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
index 40f7895..e326c1e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
@@ -16,52 +16,80 @@
 
 package com.android.settings.bluetooth;
 
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
+import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Pair;
+import android.widget.TextView;
 
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
+import java.util.concurrent.Executor;
+
 /** Tests for {@link BluetoothDevicePairingDetailBase}. */
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {
         ShadowBluetoothAdapter.class,
+        ShadowAlertDialogCompat.class,
         com.android.settings.testutils.shadow.ShadowFragment.class,
 })
 public class BluetoothDevicePairingDetailBaseTest {
 
     @Rule
-    public final MockitoRule mockito = MockitoJUnit.rule();
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     public static final String KEY_DEVICE_LIST_GROUP = "test_key";
 
@@ -86,8 +114,12 @@
     @Before
     public void setUp() {
         mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
-        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mBluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter());
         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
         when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
         final Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
         when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
@@ -156,7 +188,87 @@
     }
 
     @Test
+    public void onDeviceBondStateChanged_bonded_pairAndJoinSharingDisabled_finish() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        setUpFragmentWithPairAndJoinSharingIntent(false);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
+
+        verify(mFragment).finish();
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_bonded_pairAndJoinSharingEnabled_handle() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        setUpFragmentWithPairAndJoinSharingIntent(true);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
+        assertThat(dialog).isNotNull();
+        TextView message = dialog.findViewById(R.id.message);
+        assertThat(message).isNotNull();
+        // TODO: use stringr res once finalized
+        assertThat(message.getText().toString()).isEqualTo(
+                "Connecting to " + TEST_DEVICE_ADDRESS + "...");
+        verify(mFragment, never()).finish();
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_bonding_pairAndJoinSharingDisabled_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        setUpFragmentWithPairAndJoinSharingIntent(false);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
+
+        verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(any(BluetoothDevice.class),
+                any(Executor.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_bonding_pairAndJoinSharingEnabled_addListener() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        setUpFragmentWithPairAndJoinSharingIntent(true);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
+
+        verify(mBluetoothAdapter).addOnMetadataChangedListener(eq(mBluetoothDevice),
+                any(Executor.class),
+                any(BluetoothAdapter.OnMetadataChangedListener.class));
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingDisabled_doNothing() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
+
+        verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(
+                any(BluetoothDevice.class), any(BluetoothAdapter.OnMetadataChangedListener.class));
+    }
+
+    @Test
+    public void onDeviceBondStateChanged_unbonded_pairAndJoinSharingEnabled_removeListener() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        setUpFragmentWithPairAndJoinSharingIntent(true);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDING);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_NONE);
+
+        verify(mBluetoothAdapter).removeOnMetadataChangedListener(eq(mBluetoothDevice),
+                any(BluetoothAdapter.OnMetadataChangedListener.class));
+    }
+
+    @Test
     public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
         mFragment.mSelectedList.add(mBluetoothDevice);
         mFragment.mSelectedList.add(device);
@@ -165,13 +277,43 @@
         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
 
         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+                BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
 
         verify(mFragment).finish();
     }
 
     @Test
+    public void
+            onProfileConnectionStateChanged_deviceInSelectedListAndConnected_pairAndJoinSharing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        setUpFragmentWithPairAndJoinSharingIntent(true);
+        mFragment.onDeviceBondStateChanged(mCachedBluetoothDevice, BluetoothDevice.BOND_BONDED);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
+
+        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mFragment.getActivity()).setResult(eq(Activity.RESULT_OK), captor.capture());
+        Intent intent = captor.getValue();
+        BluetoothDevice btDevice =
+                intent != null
+                        ? intent.getParcelableExtra(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE,
+                        BluetoothDevice.class)
+                        : null;
+        assertThat(btDevice).isNotNull();
+        assertThat(btDevice).isEqualTo(mBluetoothDevice);
+        verify(mFragment).finish();
+    }
+
+    @Test
     public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
         mFragment.mSelectedList.add(device);
 
@@ -179,13 +321,14 @@
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
 
         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+                BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
 
         // not crash
     }
 
     @Test
     public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
         mFragment.mSelectedList.add(mBluetoothDevice);
         mFragment.mSelectedList.add(device);
@@ -194,13 +337,14 @@
         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
 
         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
+                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.A2DP);
 
         // not crash
     }
 
     @Test
     public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         final BluetoothDevicePreference preference =
                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
                         true, BluetoothDevicePreference.SortType.TYPE_FIFO);
@@ -211,13 +355,14 @@
         when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
 
         mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+                BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
 
         assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
     }
 
     @Test
     public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
         final BluetoothDevicePreference preference =
                 new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
@@ -233,12 +378,26 @@
         when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
         when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
 
-        mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
-                BluetoothAdapter.STATE_CONNECTED);
+        mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothAdapter.STATE_CONNECTED,
+                BluetoothProfile.A2DP);
 
         // not crash
     }
 
+    private void setUpFragmentWithPairAndJoinSharingIntent(boolean enablePairAndJoinSharing) {
+        Bundle args = new Bundle();
+        args.putBoolean(EXTRA_PAIR_AND_JOIN_SHARING, enablePairAndJoinSharing);
+        Intent intent = new Intent();
+        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+        FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
+        doReturn(intent).when(activity).getIntent();
+        doReturn(activity).when(mFragment).getActivity();
+        FragmentManager fragmentManager = mock(FragmentManager.class);
+        doReturn(fragmentManager).when(mFragment).getFragmentManager();
+        mFragment.mShouldTriggerAudioSharingShareThenPairFlow =
+                mFragment.shouldTriggerAudioSharingShareThenPairFlow();
+    }
+
     private static class TestBluetoothDevicePairingDetailBase extends
             BluetoothDevicePairingDetailBase {
 
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
index 7d8846d..1ce3316 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragmentTest.java
@@ -16,16 +16,29 @@
 
 package com.android.settings.connecteddevice.audiosharing;
 
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE;
+import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
+import android.app.Activity;
 import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.View;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -33,24 +46,29 @@
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settings.testutils.shadow.ShadowFragment;
 import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 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;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowFragment.class})
+@Config(shadows = {ShadowFragment.class, ShadowBluetoothAdapter.class})
 public class AudioSharingDashboardFragmentTest {
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock private SettingsActivity mActivity;
     @Mock private SettingsMainSwitchBar mSwitchBar;
@@ -59,11 +77,19 @@
     @Mock private AudioSharingCallAudioPreferenceController mCallAudioController;
     @Mock private AudioSharingPlaySoundPreferenceController mPlaySoundController;
     @Mock private AudioStreamsCategoryController mStreamsCategoryController;
+    @Mock private AudioSharingSwitchBarController mSwitchBarController;
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private AudioSharingDashboardFragment mFragment;
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
 
     @Before
     public void setUp() {
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        mShadowBluetoothAdapter.setEnabled(true);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
         when(mSwitchBar.getRootView()).thenReturn(mView);
         mFragment = new AudioSharingDashboardFragment();
     }
@@ -101,12 +127,72 @@
     }
 
     @Test
+    public void onActivityResult_shareThenPairWithBadCode_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mFragment.setControllers(
+                mVolumeGroupController,
+                mCallAudioController,
+                mPlaySoundController,
+                mStreamsCategoryController,
+                mSwitchBarController);
+        Intent data = new Intent();
+        Bundle extras = new Bundle();
+        BluetoothDevice device = Mockito.mock(BluetoothDevice.class);
+        extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
+        data.putExtras(extras);
+        mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_CANCELED, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mSwitchBarController, never()).handleAutoAddSourceAfterPair(device);
+    }
+
+    @Test
+    public void onActivityResult_shareThenPairWithNoDevice_doNothing() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mFragment.setControllers(
+                mVolumeGroupController,
+                mCallAudioController,
+                mPlaySoundController,
+                mStreamsCategoryController,
+                mSwitchBarController);
+        Intent data = new Intent();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, null);
+        data.putExtras(extras);
+        mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_CANCELED, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mSwitchBarController, never()).handleAutoAddSourceAfterPair(any());
+    }
+
+    @Test
+    public void onActivityResult_shareThenPairWithDevice_handleAutoAddSource() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mFragment.setControllers(
+                mVolumeGroupController,
+                mCallAudioController,
+                mPlaySoundController,
+                mStreamsCategoryController,
+                mSwitchBarController);
+        Intent data = new Intent();
+        Bundle extras = new Bundle();
+        BluetoothDevice device = Mockito.mock(BluetoothDevice.class);
+        extras.putParcelable(EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE, device);
+        data.putExtras(extras);
+        mFragment.onActivityResult(SHARE_THEN_PAIR_REQUEST_CODE, Activity.RESULT_OK, data);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mSwitchBarController).handleAutoAddSourceAfterPair(device);
+    }
+
+    @Test
     public void onAudioSharingStateChanged_updateVisibilityForControllers() {
         mFragment.setControllers(
                 mVolumeGroupController,
                 mCallAudioController,
                 mPlaySoundController,
-                mStreamsCategoryController);
+                mStreamsCategoryController,
+                mSwitchBarController);
         mFragment.onAudioSharingStateChanged();
         verify(mVolumeGroupController).updateVisibility();
         verify(mCallAudioController).updateVisibility();
@@ -120,7 +206,8 @@
                 mVolumeGroupController,
                 mCallAudioController,
                 mPlaySoundController,
-                mStreamsCategoryController);
+                mStreamsCategoryController,
+                mSwitchBarController);
         mFragment.onAudioSharingProfilesConnected();
         verify(mVolumeGroupController).onAudioSharingProfilesConnected();
     }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
index 7227f37..dec85e4 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
@@ -34,6 +34,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
@@ -82,11 +83,6 @@
             new AudioSharingDeviceItem(TEST_DEVICE_NAME3, /* groupId= */ 3, /* isActive= */ false);
     private static final AudioSharingDialogFragment.DialogEventListener EMPTY_EVENT_LISTENER =
             new AudioSharingDialogFragment.DialogEventListener() {
-                @Override
-                public void onItemClick(AudioSharingDeviceItem item) {}
-
-                @Override
-                public void onCancelClick() {}
             };
     private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
     private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
@@ -176,8 +172,17 @@
     @Test
     public void onCreateDialog_noExtraConnectedDevice_pairNewDevice() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AtomicBoolean isPairBtnClicked = new AtomicBoolean(false);
         AudioSharingDialogFragment.show(
-                mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
+                mParent,
+                new ArrayList<>(),
+                new AudioSharingDialogFragment.DialogEventListener() {
+                    @Override
+                    public void onPositiveClick() {
+                        isPairBtnClicked.set(true);
+                    }
+                },
+                TEST_EVENT_DATA_LIST);
         shadowMainLooper().idle();
         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
         assertThat(dialog).isNotNull();
@@ -191,14 +196,24 @@
                         any(Context.class),
                         eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_POSITIVE_BTN_CLICKED),
                         eq(TEST_EVENT_DATA));
+        assertThat(isPairBtnClicked.get()).isTrue();
         assertThat(dialog.isShowing()).isFalse();
     }
 
     @Test
     public void onCreateDialog_noExtraConnectedDevice_showQRCode() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        AtomicBoolean isQrCodeBtnClicked = new AtomicBoolean(false);
         AudioSharingDialogFragment.show(
-                mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
+                mParent,
+                new ArrayList<>(),
+                new AudioSharingDialogFragment.DialogEventListener() {
+                    @Override
+                    public void onCancelClick() {
+                        isQrCodeBtnClicked.set(true);
+                    }
+                },
+                TEST_EVENT_DATA_LIST);
         shadowMainLooper().idle();
         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
         assertThat(dialog).isNotNull();
@@ -212,6 +227,7 @@
                         any(Context.class),
                         eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
                         eq(TEST_EVENT_DATA));
+        assertThat(isQrCodeBtnClicked.get()).isTrue();
         assertThat(dialog.isShowing()).isFalse();
     }
 
@@ -286,12 +302,9 @@
                 list,
                 new AudioSharingDialogFragment.DialogEventListener() {
                     @Override
-                    public void onItemClick(AudioSharingDeviceItem item) {
+                    public void onItemClick(@NonNull AudioSharingDeviceItem item) {
                         isShareBtnClicked.set(true);
                     }
-
-                    @Override
-                    public void onCancelClick() {}
                 },
                 TEST_EVENT_DATA_LIST);
         shadowMainLooper().idle();
@@ -360,9 +373,6 @@
                 list,
                 new AudioSharingDialogFragment.DialogEventListener() {
                     @Override
-                    public void onItemClick(AudioSharingDeviceItem item) {}
-
-                    @Override
                     public void onCancelClick() {
                         isCancelBtnClicked.set(true);
                     }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
index 7f17291..67cb2aa 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingIncompatibleDialogFragmentTest.java
@@ -18,7 +18,6 @@
 
 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;
@@ -34,7 +33,6 @@
 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;
@@ -42,7 +40,6 @@
 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;
@@ -62,7 +59,6 @@
     @Rule public final MockitoRule mocks = MockitoJUnit.rule();
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
-    @Mock private CachedBluetoothDevice mCachedBluetoothDevice;
     private Fragment mParent;
     private AudioSharingIncompatibleDialogFragment mFragment;
 
@@ -76,7 +72,6 @@
                 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= */
@@ -97,7 +92,7 @@
     @Test
     public void onCreateDialog_flagOff_dialogNotExist() {
         mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-        AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+        AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
                 EMPTY_EVENT_LISTENER);
         shadowMainLooper().idle();
         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -107,7 +102,7 @@
     @Test
     public void onCreateDialog_unattachedFragment_dialogNotExist() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-        AudioSharingIncompatibleDialogFragment.show(new Fragment(), mCachedBluetoothDevice,
+        AudioSharingIncompatibleDialogFragment.show(new Fragment(), TEST_DEVICE_NAME,
                 EMPTY_EVENT_LISTENER);
         shadowMainLooper().idle();
         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -117,7 +112,7 @@
     @Test
     public void onCreateDialog_flagOn_showDialog() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-        AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+        AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
                 EMPTY_EVENT_LISTENER);
         shadowMainLooper().idle();
         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -134,7 +129,7 @@
     public void onCreateDialog_clickBtn_callbackTriggered() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
         AtomicBoolean isBtnClicked = new AtomicBoolean(false);
-        AudioSharingIncompatibleDialogFragment.show(mParent, mCachedBluetoothDevice,
+        AudioSharingIncompatibleDialogFragment.show(mParent, TEST_DEVICE_NAME,
                 () -> isBtnClicked.set(true));
         shadowMainLooper().idle();
         AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
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 0d21f18..eb2083e 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -934,6 +934,19 @@
         childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss());
     }
 
+    @Test
+    public void handleAutoAddSourceAfterPair() {
+        when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1));
+        when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
+        mController.handleAutoAddSourceAfterPair(mDevice1);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false);
+        List<Fragment> childFragments = mParentFragment.getChildFragmentManager().getFragments();
+        assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly(
+                AudioSharingLoadingStateDialogFragment.class.getName());
+    }
+
     private Fragment setUpFragmentWithStartSharingIntent() {
         Bundle args = new Bundle();
         args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true);
diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
deleted file mode 100644
index 1823d6d..0000000
--- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.network;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.euicc.EuiccManager;
-import android.text.TextUtils;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.Settings.MobileNetworkActivity;
-import com.android.settings.widget.AddPreference;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-public class MobileNetworkSummaryControllerTest {
-
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private SubscriptionManager mSubscriptionManager;
-    @Mock
-    private EuiccManager mEuiccManager;
-    @Mock
-    private PreferenceScreen mPreferenceScreen;
-    @Mock
-    private MobileNetworkRepository mMobileNetworkRepository;
-    @Mock
-    private MobileNetworkRepository.MobileNetworkCallback mMobileNetworkCallback;
-
-    private AddPreference mPreference;
-    private Context mContext;
-    private MobileNetworkSummaryController mController;
-    private LifecycleOwner mLifecycleOwner;
-    private Lifecycle mLifecycle;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application);
-        doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
-        doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
-        doReturn(mEuiccManager).when(mContext).getSystemService(EuiccManager.class);
-        mMobileNetworkRepository = MobileNetworkRepository.getInstance(mContext);
-        mLifecycleOwner = () -> mLifecycle;
-        mLifecycle = new Lifecycle(mLifecycleOwner);
-        mMobileNetworkRepository.addRegister(mLifecycleOwner, mMobileNetworkCallback,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
-        when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
-        when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
-        when(mEuiccManager.isEnabled()).thenReturn(true);
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1);
-
-        mController = new MobileNetworkSummaryController(mContext, mLifecycle, mLifecycleOwner);
-        mPreference = spy(new AddPreference(mContext, null));
-        mPreference.setKey(mController.getPreferenceKey());
-        when(mPreferenceScreen.findPreference(eq(mController.getPreferenceKey()))).thenReturn(
-                mPreference);
-    }
-
-    @After
-    public void tearDown() {
-        mMobileNetworkRepository.removeRegister(mMobileNetworkCallback);
-        SubscriptionUtil.setActiveSubscriptionsForTesting(null);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(null);
-    }
-
-    @Test
-    public void getSummary_noSubscriptions_returnSummaryCorrectly() {
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        assertThat(mController.getSummary()).isEqualTo("Add a network");
-    }
-
-    @Test
-    public void getSummary_noSubscriptionsNoEuiccMgr_correctSummaryAndClickHandler() {
-        when(mEuiccManager.isEnabled()).thenReturn(false);
-        assertThat(TextUtils.isEmpty(mController.getSummary())).isTrue();
-        assertThat(mPreference.getOnPreferenceClickListener()).isNull();
-        assertThat(mPreference.getFragment()).isNull();
-    }
-
-    @Test
-    @Ignore
-    public void getSummary_oneSubscription_correctSummaryAndClickHandler() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        when(sub1.getSubscriptionId()).thenReturn(1);
-        when(sub1.getDisplayName()).thenReturn("sub1");
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        assertThat(mController.getSummary()).isEqualTo("sub1");
-        assertThat(mPreference.getFragment()).isNull();
-        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        doNothing().when(mContext).startActivity(intentCaptor.capture());
-        mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
-        Intent intent = intentCaptor.getValue();
-        assertThat(intent.getComponent().getClassName()).isEqualTo(
-                MobileNetworkActivity.class.getName());
-        assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId());
-    }
-
-    @Test
-    @Ignore
-    public void getSummary_oneInactivePSim_cannotDisablePsim_correctSummaryAndClickHandler() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        when(sub1.getSubscriptionId()).thenReturn(1);
-        when(sub1.getDisplayName()).thenReturn("sub1");
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
-
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        assertThat(mController.getSummary()).isEqualTo("Tap to activate sub1");
-
-        assertThat(mPreference.getFragment()).isNull();
-        mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
-        verify(mSubscriptionManager).setSubscriptionEnabled(eq(sub1.getSubscriptionId()), eq(true));
-    }
-
-    @Test
-    @Ignore
-    public void getSummary_oneInactivePSim_canDisablePsim_correctSummaryAndClickHandler() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        when(sub1.getSubscriptionId()).thenReturn(1);
-        when(sub1.getDisplayName()).thenReturn("sub1");
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        SubscriptionUtil.setActiveSubscriptionsForTesting(Arrays.asList(sub1));
-        when(mSubscriptionManager.isActiveSubscriptionId(eq(1))).thenReturn(false);
-        when(mSubscriptionManager.canDisablePhysicalSubscription()).thenReturn(true);
-
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        assertThat(mController.getSummary()).isEqualTo("sub1");
-
-        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        doNothing().when(mContext).startActivity(intentCaptor.capture());
-        mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference);
-        Intent intent = intentCaptor.getValue();
-        assertThat(intent.getComponent().getClassName()).isEqualTo(
-                MobileNetworkActivity.class.getName());
-        assertThat(intent.getIntExtra(Settings.EXTRA_SUB_ID,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID)).isEqualTo(sub1.getSubscriptionId());
-    }
-
-    @Test
-    public void addButton_noSubscriptionsNoEuiccMgr_noAddClickListener() {
-        when(mEuiccManager.isEnabled()).thenReturn(false);
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        verify(mPreference, never()).setOnAddClickListener(notNull());
-    }
-
-    @Test
-    public void addButton_oneSubscriptionNoEuiccMgr_noAddClickListener() {
-        when(mEuiccManager.isEnabled()).thenReturn(false);
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        verify(mPreference, never()).setOnAddClickListener(notNull());
-    }
-
-    @Test
-    public void addButton_noSubscriptions_noAddClickListener() {
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        verify(mPreference, never()).setOnAddClickListener(notNull());
-    }
-
-    @Test
-    @Ignore
-    public void addButton_oneSubscription_hasAddClickListener() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        verify(mPreference).setOnAddClickListener(notNull());
-    }
-
-    @Test
-    @Ignore
-    public void addButton_twoSubscriptions_hasAddClickListener() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        final SubscriptionInfo sub2 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1, sub2));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        verify(mPreference).setOnAddClickListener(notNull());
-    }
-
-    @Test
-    @Ignore
-    public void addButton_oneSubscriptionAirplaneModeTurnedOn_addButtonGetsDisabled() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-        mController.onAirplaneModeChanged(true);
-
-        final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
-        verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
-        assertThat(captor.getValue()).isFalse();
-    }
-
-    @Test
-    @Ignore
-    public void onResume_oneSubscriptionAirplaneMode_isDisabled() {
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        assertThat(mPreference.isEnabled()).isFalse();
-
-        final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
-        verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
-        assertThat(captor.getValue()).isFalse();
-    }
-
-    @Test
-    public void onAvailableSubInfoChanged_noSubscriptionEsimDisabled_isDisabled() {
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-        when(mEuiccManager.isEnabled()).thenReturn(false);
-        mController.displayPreference(mPreferenceScreen);
-
-        mController.onAvailableSubInfoChanged(null);
-
-        assertThat(mPreference.isEnabled()).isFalse();
-    }
-
-    @Test
-    public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOn_isDisabled() {
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        assertThat(mPreference.isEnabled()).isTrue();
-
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-        mController.onAirplaneModeChanged(true);
-
-        assertThat(mPreference.isEnabled()).isFalse();
-    }
-
-    @Test
-    @Ignore
-    public void onAirplaneModeChanged_oneSubscriptionAirplaneModeGetsTurnedOff_isEnabled() {
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-        final SubscriptionInfo sub1 = mock(SubscriptionInfo.class);
-        SubscriptionUtil.setAvailableSubscriptionsForTesting(Arrays.asList(sub1));
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-
-        assertThat(mPreference.isEnabled()).isFalse();
-
-        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-        mController.onAirplaneModeChanged(false);
-
-        assertThat(mPreference.isEnabled()).isTrue();
-
-        final ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
-        verify(mPreference, atLeastOnce()).setAddWidgetEnabled(eq(false));
-        verify(mPreference, atLeastOnce()).setAddWidgetEnabled(captor.capture());
-        assertThat(captor.getValue()).isTrue();
-    }
-
-    @Test
-    public void onResume_disabledByAdmin_prefStaysDisabled() {
-        mPreference.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
-        mController.displayPreference(mPreferenceScreen);
-        mController.onResume();
-        verify(mPreference, never()).setEnabled(eq(true));
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java
index 1aecad5..a04a14d 100644
--- a/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/NotificationRingtonePreferenceControllerTest.java
@@ -18,35 +18,77 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
 import android.media.RingtoneManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.server.notification.Flags;
+import com.android.settings.R;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
 public class NotificationRingtonePreferenceControllerTest {
 
     private NotificationRingtonePreferenceController mController;
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new NotificationRingtonePreferenceController(RuntimeEnvironment.application);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        mController = new NotificationRingtonePreferenceController(mMockContext);
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
     public void isAvailable_byDefault_isTrue() {
+        when(mMockResources
+                .getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported))
+                .thenReturn(false);
+        when(mMockResources.getBoolean(R.bool.config_show_notification_ringtone))
+                .thenReturn(true);
+
         assertThat(mController.isAvailable()).isTrue();
     }
 
     @Test
     @Config(qualifiers = "mcc999")
+    @DisableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
     public void isAvailable_whenNotVisible_isFalse() {
+        when(mMockResources
+                .getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported))
+                .thenReturn(false);
+        when(mMockResources.getBoolean(R.bool.config_show_notification_ringtone))
+                .thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NOTIFICATION_VIBRATION_IN_SOUND_URI)
+    public void isAvailable_whenFlagsNotificationVibrationInSoundUri_isFalse() {
+        when(mMockResources
+                .getBoolean(com.android.internal.R.bool.config_ringtoneVibrationSettingsSupported))
+                .thenReturn(true);
+        when(mMockResources.getBoolean(R.bool.config_show_notification_ringtone))
+                .thenReturn(true);
+
         assertThat(mController.isAvailable()).isFalse();
     }
 
diff --git a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java
index 9727dd1..8442a37 100644
--- a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutPreferenceControllerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.settings.shortcut;
 
-import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -101,10 +101,10 @@
         when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
                 .thenReturn(new Intent().putExtra("d1", "d2"));
 
-        final Intent intent = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE)
+        final Intent intent = new Intent(Shortcuts.SHORTCUT_PROBE)
                 .setClass(mContext, Settings.ManageApplicationsActivity.class);
         final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0);
-        final Intent result = mController.createResultIntent(intent, ri, "mock");
+        final Intent result = mController.createResultIntent(ri);
 
         assertThat(result.getStringExtra("d1")).isEqualTo("d2");
         assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull();
@@ -131,7 +131,7 @@
         ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
 
         mPackageManager.setResolveInfosForIntent(
-                new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
+                new Intent(Shortcuts.SHORTCUT_PROBE),
                 Arrays.asList(ri1, ri2));
 
         doReturn(false).when(mController).canShowWifiHotspot();
@@ -158,7 +158,7 @@
         ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
 
         mPackageManager.setResolveInfosForIntent(
-                new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
+                new Intent(Shortcuts.SHORTCUT_PROBE),
                 Arrays.asList(ri1, ri2));
 
         doReturn(false).when(mController).canShowWifiHotspot();
@@ -276,7 +276,7 @@
         ri.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
 
         mPackageManager.setResolveInfosForIntent(
-                new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE),
+                new Intent(Shortcuts.SHORTCUT_PROBE),
                 Arrays.asList(ri));
     }
 }
diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java
new file mode 100644
index 0000000..a347ff9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.shortcut;
+
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_PROBE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+
+import com.android.settings.Settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class ShortcutsTest {
+
+    private Context mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.getApplication();
+    }
+
+    @Test
+    public void shortcutsUpdateTask() {
+        final Intent intent = new Intent(SHORTCUT_PROBE)
+                .setClass(mContext, Settings.ManageApplicationsActivity.class);
+        final ResolveInfo ri = mContext.getPackageManager().resolveActivity(intent, 0);
+        assertThat(ri).isNotNull();
+
+        ShortcutInfo shortcut = Shortcuts.createShortcutInfo(mContext, ri);
+
+        assertThat(shortcut.getLabel()).isNotNull();
+        assertThat(shortcut.getLabel().toString()).isEqualTo("App info");
+
+        assertThat(shortcut.getIntent()).isNotNull();
+        assertThat(shortcut.getIntent().getAction()).isEqualTo(Intent.ACTION_MAIN);
+        assertThat(shortcut.getIntent().getCategories()).contains("com.android.settings.SHORTCUT");
+        assertThat(shortcut.getIntent().getComponent()).isEqualTo(
+                new ComponentName(mContext, Settings.ManageApplicationsActivity.class));
+        assertThat(shortcut.getIcon()).isNotNull();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java
deleted file mode 100644
index 8352e7a..0000000
--- a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdateTaskTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.shortcut;
-
-import static com.android.settings.shortcut.CreateShortcutPreferenceController.SHORTCUT_ID_PREFIX;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-
-import com.android.settings.Settings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowPackageManager;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-public class ShortcutsUpdateTaskTest {
-
-    private Context mContext;
-    private ShadowPackageManager mPackageManager;
-
-    @Mock
-    private ShortcutManager mShortcutManager;
-    @Captor
-    private ArgumentCaptor<List<ShortcutInfo>> mListCaptor;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
-        mPackageManager = Shadow.extract(mContext.getPackageManager());
-    }
-
-    @Test
-    public void shortcutsUpdateTask() {
-        mContext = spy(RuntimeEnvironment.application);
-        doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
-        final Intent shortcut1 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE)
-                .setComponent(new ComponentName(
-                        mContext, Settings.ManageApplicationsActivity.class));
-        final ResolveInfo ri1 = mock(ResolveInfo.class);
-        ri1.nonLocalizedLabel = "label1";
-
-        final Intent shortcut2 = new Intent(CreateShortcutPreferenceController.SHORTCUT_PROBE)
-                .setComponent(new ComponentName(
-                        mContext, Settings.SoundSettingsActivity.class));
-        final ResolveInfo ri2 = mock(ResolveInfo.class);
-        ri2.nonLocalizedLabel = "label2";
-
-        mPackageManager.addResolveInfoForIntent(shortcut1, ri1);
-        mPackageManager.addResolveInfoForIntent(shortcut2, ri2);
-
-        final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
-                makeShortcut("d1"),
-                makeShortcut("d2"),
-                makeShortcut(Settings.ManageApplicationsActivity.class),
-                makeShortcut("d3"),
-                makeShortcut(Settings.SoundSettingsActivity.class));
-        when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
-
-        new ShortcutsUpdateTask(mContext).doInBackground();
-
-        verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
-
-        final List<ShortcutInfo> updates = mListCaptor.getValue();
-
-        assertThat(updates).hasSize(2);
-        assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId());
-        assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId());
-    }
-
-    private ShortcutInfo makeShortcut(Class<?> className) {
-        ComponentName cn = new ComponentName(mContext, className);
-        return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString());
-    }
-
-    private ShortcutInfo makeShortcut(String id) {
-        return new ShortcutInfo.Builder(mContext, id).build();
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java
new file mode 100644
index 0000000..5324ff5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/shortcut/ShortcutsUpdaterTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.shortcut;
+
+import static com.android.settings.shortcut.Shortcuts.SHORTCUT_ID_PREFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Flags;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.settings.Settings;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class ShortcutsUpdaterTest {
+
+    private Context mContext;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private ShortcutManager mShortcutManager;
+    @Captor
+    private ArgumentCaptor<List<ShortcutInfo>> mListCaptor;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
+    }
+
+    @Test
+    public void updatePinnedShortcuts_updatesAllShortcuts() {
+        final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
+                makeShortcut("d1"),
+                makeShortcut("d2"),
+                makeShortcut(Settings.ManageApplicationsActivity.class),
+                makeShortcut("d3"),
+                makeShortcut(Settings.SoundSettingsActivity.class));
+        when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);
+
+        ShortcutsUpdater.updatePinnedShortcuts(mContext);
+
+        verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
+
+        final List<ShortcutInfo> updates = mListCaptor.getValue();
+
+        assertThat(updates).hasSize(2);
+        assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId());
+        assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId());
+        assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("App info");
+        assertThat(updates.get(1).getShortLabel().toString()).isEqualTo("Sound & vibration");
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    public void updatePinnedShortcuts_withModesFlag_replacesDndByModes() {
+        List<ShortcutInfo> shortcuts = List.of(
+                makeShortcut(Settings.ZenModeSettingsActivity.class));
+        when(mShortcutManager.getPinnedShortcuts()).thenReturn(shortcuts);
+
+        ShortcutsUpdater.updatePinnedShortcuts(mContext);
+
+        verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
+        final List<ShortcutInfo> updates = mListCaptor.getValue();
+        assertThat(updates).hasSize(1);
+
+        // Id hasn't changed, but intent and label has.
+        ComponentName zenCn = new ComponentName(mContext, Settings.ZenModeSettingsActivity.class);
+        ComponentName modesCn = new ComponentName(mContext, Settings.ModesSettingsActivity.class);
+        assertThat(updates.get(0).getId()).isEqualTo(
+                SHORTCUT_ID_PREFIX + zenCn.flattenToShortString());
+        assertThat(updates.get(0).getIntent().getComponent()).isEqualTo(modesCn);
+        assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("Modes");
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    public void updatePinnedShortcuts_withoutModesFlag_leavesDndAlone() {
+        List<ShortcutInfo> shortcuts = List.of(
+                makeShortcut(Settings.ZenModeSettingsActivity.class));
+        when(mShortcutManager.getPinnedShortcuts()).thenReturn(shortcuts);
+
+        ShortcutsUpdater.updatePinnedShortcuts(mContext);
+
+        verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());
+        final List<ShortcutInfo> updates = mListCaptor.getValue();
+        assertThat(updates).hasSize(1);
+
+        // Nothing has changed.
+        ComponentName zenCn = new ComponentName(mContext, Settings.ZenModeSettingsActivity.class);
+        assertThat(updates.get(0).getId()).isEqualTo(
+                SHORTCUT_ID_PREFIX + zenCn.flattenToShortString());
+        assertThat(updates.get(0).getIntent().getComponent()).isEqualTo(zenCn);
+        assertThat(updates.get(0).getShortLabel().toString()).isEqualTo("Do Not Disturb");
+
+    }
+
+    private ShortcutInfo makeShortcut(Class<?> className) {
+        ComponentName cn = new ComponentName(mContext, className);
+        return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString());
+    }
+
+    private ShortcutInfo makeShortcut(String id) {
+        return new ShortcutInfo.Builder(mContext, id).build();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java b/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java
index 3f6d785..230b443 100644
--- a/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java
+++ b/tests/robotests/src/com/android/settings/wallpaper/WallpaperSuggestionActivityTest.java
@@ -118,7 +118,7 @@
     }
 
     @Test
-    public void addExtras_intentNotFromSetupWizard_extrasHasFocusWallpaper() {
+    public void addExtras_intentNotFromSetupWizard_extrasHasFocusWallpaperAndLaunchedSettingsSearch() {
         WallpaperSuggestionActivity activity = Robolectric.buildActivity(
                 WallpaperSuggestionActivity.class, new Intent(Intent.ACTION_MAIN).setComponent(
                         new ComponentName(RuntimeEnvironment.application,
@@ -127,6 +127,8 @@
 
         assertThat(intent).isNotNull();
         assertThat(intent.getStringExtra(WALLPAPER_FLAVOR)).isEqualTo("focus_wallpaper");
+        assertThat(intent.getStringExtra(WALLPAPER_LAUNCH_SOURCE))
+                .isEqualTo("app_launched_settings_search");
     }
 
 
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt
new file mode 100644
index 0000000..69fa9c4
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryControllerTest.kt
@@ -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.network
+
+import android.content.Context
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.preference.PreferenceManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.RestrictedPreference
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+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.stub
+
+@RunWith(AndroidJUnit4::class)
+class MobileNetworkSummaryControllerTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val preference = RestrictedPreference(context).apply { key = KEY }
+    private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
+
+    private val mockMobileNetworkSummaryRepository = mock<MobileNetworkSummaryRepository>()
+    private val airplaneModeOnFlow = MutableStateFlow(false)
+
+    private val controller =
+        MobileNetworkSummaryController(
+            context = context,
+            preferenceKey = KEY,
+            repository = mockMobileNetworkSummaryRepository,
+            airplaneModeOnFlow = airplaneModeOnFlow,
+        )
+
+    @Before
+    fun setUp() {
+        preferenceScreen.addPreference(preference)
+        controller.displayPreference(preferenceScreen)
+    }
+
+    @Test
+    fun onViewCreated_noSubscriptions(): Unit = runBlocking {
+        mockMobileNetworkSummaryRepository.stub {
+            on { subscriptionsStateFlow() } doReturn
+                flowOf(MobileNetworkSummaryRepository.NoSubscriptions)
+        }
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.summary).isNull()
+        assertThat(preference.isEnabled).isFalse()
+        assertThat(preference.onPreferenceClickListener).isNull()
+    }
+
+    @Test
+    fun onViewCreated_addNetwork(): Unit = runBlocking {
+        mockMobileNetworkSummaryRepository.stub {
+            on { subscriptionsStateFlow() } doReturn
+                flowOf(MobileNetworkSummaryRepository.AddNetwork)
+        }
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.summary)
+            .isEqualTo(context.getString(R.string.mobile_network_summary_add_a_network))
+        assertThat(preference.isEnabled).isTrue()
+        assertThat(preference.onPreferenceClickListener).isNotNull()
+    }
+
+    @Test
+    fun onViewCreated_hasSubscriptions(): Unit = runBlocking {
+        mockMobileNetworkSummaryRepository.stub {
+            on { subscriptionsStateFlow() } doReturn
+                flowOf(
+                    MobileNetworkSummaryRepository.HasSubscriptions(
+                        displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
+                    )
+                )
+        }
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.summary).isEqualTo("$DISPLAY_NAME_1, $DISPLAY_NAME_2")
+        assertThat(preference.isEnabled).isTrue()
+        assertThat(preference.fragment).isNotNull()
+    }
+
+    @Test
+    fun onViewCreated_addNetworkAndAirplaneModeOn(): Unit = runBlocking {
+        mockMobileNetworkSummaryRepository.stub {
+            on { subscriptionsStateFlow() } doReturn
+                flowOf(MobileNetworkSummaryRepository.AddNetwork)
+        }
+        airplaneModeOnFlow.value = true
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.isEnabled).isFalse()
+    }
+
+    @Test
+    fun onViewCreated_hasSubscriptionsAndAirplaneModeOn(): Unit = runBlocking {
+        mockMobileNetworkSummaryRepository.stub {
+            on { subscriptionsStateFlow() } doReturn
+                flowOf(
+                    MobileNetworkSummaryRepository.HasSubscriptions(
+                        displayNames = listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
+                    )
+                )
+        }
+        airplaneModeOnFlow.value = true
+
+        controller.onViewCreated(TestLifecycleOwner())
+        delay(100)
+
+        assertThat(preference.isEnabled).isFalse()
+    }
+
+
+    private companion object {
+        const val KEY = "test_key"
+        const val DISPLAY_NAME_1 = "Display Name 1"
+        const val DISPLAY_NAME_2 = "Display Name 2"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt
new file mode 100644
index 0000000..463af96
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/MobileNetworkSummaryRepositoryTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network
+
+import android.content.Context
+import android.telephony.SubscriptionInfo
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.telephony.SubscriptionRepository
+import com.android.settings.network.telephony.euicc.EuiccRepository
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class MobileNetworkSummaryRepositoryTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val mockSubscriptionRepository = mock<SubscriptionRepository>()
+    private val mockEuiccRepository = mock<EuiccRepository>()
+
+    private val repository =
+        MobileNetworkSummaryRepository(
+            context = context,
+            subscriptionRepository = mockSubscriptionRepository,
+            euiccRepository = mockEuiccRepository,
+            getDisplayName = { it.displayName.toString() },
+        )
+
+    @Test
+    fun subscriptionsStateFlow_noSubscriptionsAndShowEuicc_returnsAddNetwork() = runBlocking {
+        mockSubscriptionRepository.stub {
+            on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList())
+        }
+        mockEuiccRepository.stub { on { showEuiccSettings() } doReturn true }
+
+        val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
+
+        assertThat(state).isEqualTo(MobileNetworkSummaryRepository.AddNetwork)
+    }
+
+    @Test
+    fun subscriptionsStateFlow_noSubscriptionsAndHideEuicc_returnsNoSubscriptions() = runBlocking {
+        mockSubscriptionRepository.stub {
+            on { selectableSubscriptionInfoListFlow() } doReturn flowOf(emptyList())
+        }
+        mockEuiccRepository.stub { on { showEuiccSettings() } doReturn false }
+
+        val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
+
+        assertThat(state).isEqualTo(MobileNetworkSummaryRepository.NoSubscriptions)
+    }
+
+    @Test
+    fun subscriptionsStateFlow_hasSubscriptions_returnsHasSubscriptions() = runBlocking {
+        mockSubscriptionRepository.stub {
+            on { selectableSubscriptionInfoListFlow() } doReturn
+                flowOf(
+                    listOf(
+                        SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_1).build(),
+                        SubscriptionInfo.Builder().setDisplayName(DISPLAY_NAME_2).build(),
+                    )
+                )
+        }
+
+        val state = repository.subscriptionsStateFlow().firstWithTimeoutOrNull()
+
+        assertThat(state)
+            .isEqualTo(
+                MobileNetworkSummaryRepository.HasSubscriptions(
+                    listOf(DISPLAY_NAME_1, DISPLAY_NAME_2)
+                )
+            )
+    }
+
+    private companion object {
+        const val DISPLAY_NAME_1 = "Sub 1"
+        const val DISPLAY_NAME_2 = "Sub 2"
+    }
+}
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 90d0aa5..265cd33 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
@@ -27,7 +27,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.stub
@@ -135,13 +137,35 @@
     }
 
     @Test
+    fun isVoNrEnabledFlow_noPhoneProcess_noCrash() = runBlocking {
+        mockTelephonyManager.stub { on { isVoNrEnabled } doThrow IllegalStateException("no Phone") }
+
+        val isVoNrEnabled = repository.isVoNrEnabledFlow(SUB_ID).firstWithTimeoutOrNull()
+
+        assertThat(isVoNrEnabled).isFalse()
+    }
+
+    @Test
     fun setVoNrEnabled(): Unit = runBlocking {
         repository.setVoNrEnabled(SUB_ID, true)
 
         verify(mockTelephonyManager).setVoNrEnabled(true)
     }
 
+    @Test
+    fun setVoNrEnabled_noPhoneProcess_noCrash(): Unit = runBlocking {
+        mockTelephonyManager.stub {
+            on {
+                setVoNrEnabled(any())
+            } doThrow IllegalStateException("no Phone")
+        }
+
+        repository.setVoNrEnabled(SUB_ID, true)
+
+        verify(mockTelephonyManager).setVoNrEnabled(true)
+    }
+
     private companion object {
         const val SUB_ID = 1
     }
-}
+}
\ No newline at end of file