Merge "Swap orders of GAIA signin and lock setup during Private space setup" into main
diff --git a/Android.bp b/Android.bp
index b456a72..9b4cfde 100644
--- a/Android.bp
+++ b/Android.bp
@@ -107,13 +107,12 @@
 
     plugins: ["androidx.room_room-compiler-plugin"],
 
-    // TODO: b/307330031 - Re-enable the nullaway plugin when the issue is fixed.
-    // errorprone: {
-    //     extra_check_modules: ["//external/nullaway:nullaway_plugin"],
-    //     javacflags: [
-    //         "-XepOpt:NullAway:AnnotatedPackages=com.android.settings",
-    //     ],
-    // },
+    errorprone: {
+        extra_check_modules: ["//external/nullaway:nullaway_plugin"],
+        javacflags: [
+            "-XepOpt:NullAway:AnnotatedPackages=com.android.settings",
+        ],
+    },
 
     libs: [
         "telephony-common",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 095452c..85a443e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2875,6 +2875,8 @@
             </intent-filter>
             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                        android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
+            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+                       android:value="@string/menu_key_system"/>
         </activity>
 
         <activity
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 14a4a51..6ed0d82 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2946,9 +2946,9 @@
     <string name="imei_multi_sim_primary">IMEI (sim slot <xliff:g id="imei_slot_id_primary">%1$d</xliff:g>) (primary)</string>
     <!-- About phone screen, summary of the MAC address [CHAR LIMIT=80] -->
     <string name="view_saved_network">To view, choose saved network</string>
-    <!-- Do not translate. About phone, status item title -->
+    <!-- About phone, status item title. This translation is for specific language only. Like: fr-CA -->
     <string name="status_imei">IMEI</string>
-    <!-- Do not translate. About phone, status item title -->
+    <!-- About phone, status item title. This translation is for specific language only. Like: fr-CA -->
     <string name="status_imei_sv">IMEI SV</string>
     <!-- Do not translate. About phone, status item title -->
     <string name="status_iccid">ICCID</string>
@@ -10927,6 +10927,9 @@
     <!-- Title with application label for media output settings. [CHAR LIMIT=NONE] -->
     <string name="media_output_label_title">Play <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
 
+    <!-- Title for media output settings without media is playing -->
+    <string name="media_output_title_without_playing">Audio will play on</string>
+
     <!-- Summary for media output default settings. (this device) [CHAR LIMIT=30] -->
     <string name="media_output_default_summary">This device</string>
 
diff --git a/res/xml/bluetooth_audio_streams.xml b/res/xml/bluetooth_audio_streams.xml
index 206fb34..b419eaa 100644
--- a/res/xml/bluetooth_audio_streams.xml
+++ b/res/xml/bluetooth_audio_streams.xml
@@ -24,7 +24,8 @@
         android:key="audio_streams_scan_qr_code"
         android:title="@string/bluetooth_find_broadcast_button_scan"
         android:icon="@drawable/ic_add_24dp"
-        android:summary="@string/audio_streams_qr_code_summary" />
+        android:summary="@string/audio_streams_qr_code_summary"
+        settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController" />
 
     <Preference
         android:key="audio_streams_active_device"
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index 630acbb..27001d6 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -28,9 +28,9 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.Utils;
 import com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
 import com.android.settings.core.SettingsUIDeviceConfig;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.overlay.SurveyFeatureProvider;
 import com.android.settings.search.BaseSearchIndexProvider;
@@ -87,7 +87,7 @@
                             + ", action : "
                             + action);
         }
-        if (Flags.enableLeAudioSharing()) {
+        if (AudioSharingUtils.isFeatureEnabled()) {
             use(AudioSharingDevicePreferenceController.class).init(this);
         }
         use(AvailableMediaDeviceGroupController.class).init(this);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
index 90cf779..8497c9d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
@@ -27,7 +27,6 @@
 
 import com.android.settings.bluetooth.Utils;
 import com.android.settings.core.BasePreferenceController;
-import com.android.settings.flags.Flags;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.utils.ThreadUtils;
@@ -51,7 +50,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+        return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
     @Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index 7a2f26f..483a74b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -24,7 +24,6 @@
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -39,7 +38,6 @@
 import com.android.settings.connecteddevice.DevicePreferenceCallback;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LeAudioProfile;
@@ -309,9 +307,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
-                        && Flags.enableLeAudioSharing()
-                        && mBluetoothDeviceUpdater != null
+        return AudioSharingUtils.isFeatureEnabled() && mBluetoothDeviceUpdater != null
                 ? AVAILABLE_UNSEARCHABLE
                 : UNSUPPORTED_ON_DEVICE;
     }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index bdf7f03..5cd86b4 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -32,7 +32,6 @@
 import com.android.internal.widget.RecyclerView;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -73,7 +72,7 @@
             Fragment host,
             ArrayList<AudioSharingDeviceItem> deviceItems,
             DialogEventListener listener) {
-        if (!Flags.enableLeAudioSharing()) return;
+        if (!AudioSharingUtils.isFeatureEnabled()) return;
         final FragmentManager manager = host.getChildFragmentManager();
         sListener = listener;
         if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
index 1840f58..365aed5 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
@@ -32,7 +32,6 @@
 import com.android.internal.widget.RecyclerView;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
 
 import java.util.ArrayList;
 
@@ -66,13 +65,16 @@
      * Display the {@link AudioSharingDisconnectDialogFragment} dialog.
      *
      * @param host The Fragment this dialog will be hosted.
+     * @param deviceItems The existing connected device items in audio sharing session.
+     * @param newDeviceName The name of the latest connected device triggered this dialog.
+     * @param listener The callback to handle the user action on this dialog.
      */
     public static void show(
             Fragment host,
             ArrayList<AudioSharingDeviceItem> deviceItems,
             String newDeviceName,
             DialogEventListener listener) {
-        if (!Flags.enableLeAudioSharing()) return;
+        if (!AudioSharingUtils.isFeatureEnabled()) return;
         final FragmentManager manager = host.getChildFragmentManager();
         sListener = listener;
         if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
index b9646ac..589c955 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
@@ -30,7 +30,6 @@
 
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -69,7 +68,7 @@
             ArrayList<AudioSharingDeviceItem> deviceItems,
             String newDeviceName,
             DialogEventListener listener) {
-        if (!Flags.enableLeAudioSharing()) return;
+        if (!AudioSharingUtils.isFeatureEnabled()) return;
         final FragmentManager manager = host.getChildFragmentManager();
         sListener = listener;
         if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
index b6b4568..294e8b2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 
 import com.android.settings.core.BasePreferenceController;
-import com.android.settings.flags.Flags;
 
 public class AudioSharingPreferenceController extends BasePreferenceController {
     private static final String TAG = "AudioSharingPreferenceController";
@@ -33,6 +32,6 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+        return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
index 495fad3..13416aa 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
@@ -25,7 +25,6 @@
 import androidx.fragment.app.FragmentManager;
 
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
 
 public class AudioSharingStopDialogFragment extends InstrumentedDialogFragment {
     private static final String TAG = "AudioSharingStopDialog";
@@ -50,9 +49,11 @@
      * Display the {@link AudioSharingStopDialogFragment} dialog.
      *
      * @param host The Fragment this dialog will be hosted.
+     * @param newDeviceName The name of the latest connected device triggered this dialog.
+     * @param listener The callback to handle the user action on this dialog.
      */
     public static void show(Fragment host, String newDeviceName, DialogEventListener listener) {
-        if (!Flags.enableLeAudioSharing()) return;
+        if (!AudioSharingUtils.isFeatureEnabled()) return;
         final FragmentManager manager = host.getChildFragmentManager();
         sListener = listener;
         if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 469a387..dc04bee 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -38,7 +38,6 @@
 import com.android.settings.bluetooth.Utils;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
 import com.android.settings.widget.SettingsMainSwitchBar;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
@@ -315,7 +314,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+        return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
     /**
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index a43e098..10cac42 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -20,10 +20,12 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.settings.flags.Flags;
 import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -244,9 +246,8 @@
             Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
             return Optional.empty();
         }
-        var groupedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(manager);
-        var leadDevices =
-                AudioSharingUtils.buildOrderedConnectedLeadDevices(manager, groupedDevices, false);
+        var groupedDevices = fetchConnectedDevicesByGroupId(manager);
+        var leadDevices = buildOrderedConnectedLeadDevices(manager, groupedDevices, false);
 
         if (!leadDevices.isEmpty() && AudioSharingUtils.isActiveLeAudioDevice(leadDevices.get(0))) {
             return Optional.of(leadDevices.get(0));
@@ -262,6 +263,16 @@
                 () -> Toast.makeText(context, message, Toast.LENGTH_LONG).show());
     }
 
+    /** Returns if the le audio sharing is enabled. */
+    public static boolean isFeatureEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        return Flags.enableLeAudioSharing()
+                && adapter.isLeAudioBroadcastSourceSupported()
+                        == BluetoothStatusCodes.FEATURE_SUPPORTED
+                && adapter.isLeAudioBroadcastAssistantSupported()
+                        == BluetoothStatusCodes.FEATURE_SUPPORTED;
+    }
+
     /** Automatically update active device if needed. */
     public static void updateActiveDeviceIfNeeded(LocalBluetoothManager localBtManager) {
         if (localBtManager == null) return;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
index 47f70c7..e47e141 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
@@ -26,7 +26,6 @@
 
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
 
 import java.util.ArrayList;
 
@@ -64,7 +63,7 @@
             Fragment host,
             ArrayList<AudioSharingDeviceItem> deviceItems,
             DialogEventListener listener) {
-        if (!Flags.enableLeAudioSharing()) return;
+        if (!AudioSharingUtils.isFeatureEnabled()) return;
         final FragmentManager manager = host.getChildFragmentManager();
         sListener = listener;
         if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
index 562427f..a673cb4 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
@@ -17,6 +17,7 @@
 package com.android.settings.connecteddevice.audiosharing.audiostreams;
 
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 
 import com.android.settings.R;
@@ -58,10 +59,17 @@
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
+        use(AudioStreamsScanQrCodeController.class).setFragment(this);
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
     }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        // TODO(chelseahao): implementation.
+    }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
new file mode 100644
index 0000000..12b46e5
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+public class AudioStreamsScanQrCodeController extends BasePreferenceController
+        implements DefaultLifecycleObserver {
+    private static final String TAG = "AudioStreamsProgressCategoryController";
+    private static final boolean DEBUG = BluetoothUtils.D;
+    private static final String KEY = "audio_streams_scan_qr_code";
+    private static final int REQUEST_SCAN_BT_BROADCAST_QR_CODE = 0;
+    private final BluetoothCallback mBluetoothCallback =
+            new BluetoothCallback() {
+                @Override
+                public void onActiveDeviceChanged(
+                        @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+                    if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+                        updateVisibility();
+                    }
+                }
+            };
+
+    private final LocalBluetoothManager mLocalBtManager;
+    private AudioStreamsDashboardFragment mFragment;
+    private Preference mPreference;
+
+    public AudioStreamsScanQrCodeController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+        mLocalBtManager = Utils.getLocalBtManager(mContext);
+    }
+
+    public void setFragment(AudioStreamsDashboardFragment fragment) {
+        mFragment = fragment;
+    }
+
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        if (mLocalBtManager != null) {
+            mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
+        }
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        if (mLocalBtManager != null) {
+            mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
+        }
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
+        if (mPreference == null) {
+            Log.w(TAG, "displayPreference() mPreference is null!");
+            return;
+        }
+        mPreference.setOnPreferenceClickListener(
+                preference -> {
+                    if (mFragment == null) {
+                        Log.w(TAG, "displayPreference() mFragment is null!");
+                        return false;
+                    }
+                    if (preference.getKey().equals(KEY)) {
+                        Intent intent = new Intent(mContext, QrCodeScanModeActivity.class);
+                        intent.setAction(
+                                BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER);
+                        mFragment.startActivityForResult(intent, REQUEST_SCAN_BT_BROADCAST_QR_CODE);
+                        if (DEBUG) {
+                            Log.w(TAG, "displayPreference() sent intent : " + intent);
+                        }
+                        return true;
+                    }
+                    return false;
+                });
+    }
+
+    private void updateVisibility() {
+        ThreadUtils.postOnBackgroundThread(
+                () -> {
+                    boolean hasActiveLe =
+                            AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent();
+                    ThreadUtils.postOnMainThread(() -> mPreference.setVisible(hasActiveLe));
+                });
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
index d6d0634..091ebcb 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
@@ -16,10 +16,6 @@
 
 package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode;
 
-import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK;
-import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP;
-
-import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
@@ -44,9 +40,6 @@
     private static final boolean DEBUG = BluetoothUtils.D;
     private static final String TAG = "QrCodeScanModeActivity";
 
-    private boolean mIsGroupOp;
-    private BluetoothDevice mSink;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -88,8 +81,6 @@
             Log.d(TAG, "showQrCodeScannerFragment");
         }
 
-        mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK);
-        mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false);
         if (DEBUG) {
             Log.d(TAG, "get extra from intent");
         }
diff --git a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
index c24ad22..dfe571a 100644
--- a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
+++ b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
@@ -65,7 +65,7 @@
                         updateState(mPreference);
                     }
                 };
-        mPeakRefreshRate = findHighestRefreshRateForDefaultDisplay(context);
+        mPeakRefreshRate = Math.round(findHighestRefreshRateForDefaultDisplay(context));
         Log.d(
                 TAG,
                 "DEFAULT_REFRESH_RATE : "
diff --git a/src/com/android/settings/media/MediaOutputIndicatorSlice.java b/src/com/android/settings/media/MediaOutputIndicatorSlice.java
index e2232e7..bb075c2 100644
--- a/src/com/android/settings/media/MediaOutputIndicatorSlice.java
+++ b/src/com/android/settings/media/MediaOutputIndicatorSlice.java
@@ -17,6 +17,7 @@
 package com.android.settings.media;
 
 import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
 
 import android.annotation.ColorInt;
 import android.content.Context;
@@ -58,7 +59,12 @@
         }
         final IconCompat icon = IconCompat.createWithResource(mContext,
                 com.android.internal.R.drawable.ic_settings_bluetooth);
-        final CharSequence title = mContext.getString(R.string.media_output_label_title,
+        final int stringRes = enableOutputSwitcherForSystemRouting()
+                ? (getWorker().getActiveLocalMediaController() != null
+                        ? R.string.media_output_label_title
+                        : R.string.media_output_title_without_playing)
+                : R.string.media_output_label_title;
+        final CharSequence title = mContext.getString(stringRes,
                 Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
         final SliceAction primarySliceAction = SliceAction.create(
                 getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title);
@@ -117,28 +123,36 @@
         // 2. worker is not null
         // 3. Available devices are more than 0
         // 4. The local media session is active and the state is playing.
+        //    - if !enableOutputSwitcherForSystemRouting(), (4) will be bypass.
         return getWorker() != null
                 && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)
                 && getWorker().getMediaDevices().size() > 0
-                && getWorker().getActiveLocalMediaController() != null;
+                && (enableOutputSwitcherForSystemRouting()
+                        ? true : getWorker().getActiveLocalMediaController() != null);
     }
 
     @Override
     public void onNotifyChange(Intent intent) {
         final MediaController mediaController = getWorker().getActiveLocalMediaController();
 
-        if (mediaController == null) {
+        // Launch media output dialog
+        if (enableOutputSwitcherForSystemRouting() && mediaController == null) {
+            mContext.sendBroadcast(new Intent()
+                    .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
+                    .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG));
+        } else if (mediaController != null) {
+            mContext.sendBroadcast(new Intent()
+                    .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
+                    .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
+                    .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
+                            mediaController.getSessionToken())
+                    .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+                            mediaController.getPackageName()));
+        } else {
             Log.d(TAG, "No active local media controller");
             return;
         }
-        // Launch media output dialog
-        mContext.sendBroadcast(new Intent()
-                .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
-                .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
-                .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
-                        mediaController.getSessionToken())
-                .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
-                        mediaController.getPackageName()));
+
         // Dismiss volume panel
         mContext.sendBroadcast(new Intent()
                 .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)
diff --git a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
index c874a5e..101364b 100644
--- a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
+++ b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
@@ -15,20 +15,22 @@
  */
 package com.android.settings.security;
 
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.provider.Settings;
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
-import com.android.settings.Utils;
 import com.android.settings.core.TogglePreferenceController;
 import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
 
 /** Preference controller for content protection toggle switch bar. */
 public class ContentProtectionTogglePreferenceController extends TogglePreferenceController
@@ -37,9 +39,9 @@
     @VisibleForTesting
     static final String KEY_CONTENT_PROTECTION_PREFERENCE = "content_protection_user_consent";
 
-    private SettingsMainSwitchPreference mSwitchBar;
+    @Nullable private SettingsMainSwitchPreference mSwitchBar;
+    @Nullable private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
     private final ContentResolver mContentResolver;
-    private final boolean isFullyManagedDevice = Utils.getDeviceOwnerComponent(mContext) != null;
 
     public ContentProtectionTogglePreferenceController(Context context, String preferenceKey) {
         super(context, preferenceKey);
@@ -53,7 +55,7 @@
 
     @Override
     public boolean isChecked() {
-        if (isFullyManagedDevice) {
+        if (mEnforcedAdmin != null) {
             // If fully managed device, it should always unchecked
             return false;
         }
@@ -70,12 +72,25 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
+        final Preference preference = screen.findPreference(getPreferenceKey());
 
-        mSwitchBar = screen.findPreference(getPreferenceKey());
-        mSwitchBar.addOnSwitchChangeListener(this);
-        if (isFullyManagedDevice) {
-            // If fully managed device, the switch bar is greyed out
-            mSwitchBar.setEnabled(false);
+        if (preference instanceof SettingsMainSwitchPreference) {
+            mSwitchBar = (SettingsMainSwitchPreference) preference;
+            mSwitchBar.addOnSwitchChangeListener(this);
+        }
+    }
+
+    /**
+     * Temporary workaround for SettingsMainSwitchPreference.setDisabledByAdmin without user
+     * restriction.
+     */
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        // Assign the value to mEnforcedAdmin since it's needed in isChecked()
+        mEnforcedAdmin = getEnforcedAdmin();
+        if (mSwitchBar != null && mEnforcedAdmin != null) {
+            mSwitchBar.setDisabledByAdmin(mEnforcedAdmin);
         }
     }
 
@@ -90,4 +105,9 @@
     public int getSliceHighlightMenuRes() {
         return R.string.menu_key_security;
     }
+
+    @VisibleForTesting
+    protected RestrictedLockUtils.EnforcedAdmin getEnforcedAdmin() {
+        return RestrictedLockUtilsInternal.getDeviceOwner(mContext);
+    }
 }
diff --git a/src/com/android/settings/sound/AudioSwitchPreferenceController.java b/src/com/android/settings/sound/AudioSwitchPreferenceController.java
index 6475257..b570b2d 100644
--- a/src/com/android/settings/sound/AudioSwitchPreferenceController.java
+++ b/src/com/android/settings/sound/AudioSwitchPreferenceController.java
@@ -18,6 +18,9 @@
 
 import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION;
 
+import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
+
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -28,6 +31,8 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.MediaRouter;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.FeatureFlagUtils;
@@ -79,6 +84,8 @@
     private final WiredHeadsetBroadcastReceiver mReceiver;
     private final Handler mHandler;
     private LocalBluetoothManager mLocalBluetoothManager;
+    @Nullable private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener;
+    @Nullable private MediaSessionManager mMediaSessionManager;
 
     public interface AudioSwitchCallback {
         void onPreferenceDataChanged(ListPreference preference);
@@ -107,6 +114,14 @@
             return;
         }
         mProfileManager = mLocalBluetoothManager.getProfileManager();
+
+        if (enableOutputSwitcherForSystemRouting()) {
+            mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+            mSessionListener = new SessionChangeListener();
+        } else {
+            mMediaSessionManager = null;
+            mSessionListener = null;
+        }
     }
 
     /**
@@ -329,13 +344,27 @@
         // Register for misc other intent broadcasts.
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
         intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION);
-        mContext.registerReceiver(mReceiver, intentFilter);
+
+        if (enableOutputSwitcherForSystemRouting()) {
+            mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
+            if (mMediaSessionManager != null) {
+                mMediaSessionManager.addOnActiveSessionsChangedListener(
+                        mSessionListener, null, mHandler);
+            }
+        } else {
+            mContext.registerReceiver(mReceiver, intentFilter);
+        }
     }
 
     private void unregister() {
         mLocalBluetoothManager.getEventManager().unregisterCallback(this);
         mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);
         mContext.unregisterReceiver(mReceiver);
+        if (enableOutputSwitcherForSystemRouting()) {
+            if (mMediaSessionManager != null) {
+                mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionListener);
+            }
+        }
     }
 
     /** Notifications of audio device connection and disconnection events. */
@@ -362,4 +391,12 @@
             }
         }
     }
+
+    private class SessionChangeListener
+            implements MediaSessionManager.OnActiveSessionsChangedListener {
+        @Override
+        public void onActiveSessionsChanged(List<MediaController> controllers) {
+            updateState(mPreference);
+        }
+    }
 }
diff --git a/src/com/android/settings/sound/MediaOutputPreferenceController.java b/src/com/android/settings/sound/MediaOutputPreferenceController.java
index 758f7e3..47be7fe 100644
--- a/src/com/android/settings/sound/MediaOutputPreferenceController.java
+++ b/src/com/android/settings/sound/MediaOutputPreferenceController.java
@@ -16,6 +16,9 @@
 
 package com.android.settings.sound;
 
+import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
+
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.Intent;
@@ -46,21 +49,22 @@
  */
 public class MediaOutputPreferenceController extends AudioSwitchPreferenceController {
 
-    private MediaController mMediaController;
+    private static final String TAG = "MediaOutputPreferenceController";
+    @Nullable private MediaController mMediaController;
+    private MediaSessionManager mMediaSessionManager;
 
     public MediaOutputPreferenceController(Context context, String key) {
         super(context, key);
-        mMediaController = MediaOutputUtils.getActiveLocalMediaController(context.getSystemService(
-                MediaSessionManager.class));
+        mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+        mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager);
     }
 
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
 
-        if (!Utils.isAudioModeOngoingCall(mContext) && mMediaController != null) {
-            mPreference.setVisible(true);
-        }
+        mPreference.setVisible(!Utils.isAudioModeOngoingCall(mContext)
+                && (enableOutputSwitcherForSystemRouting() ? true : mMediaController != null));
     }
 
     @Override
@@ -70,11 +74,16 @@
             return;
         }
 
-        if (mMediaController == null) {
-            // No active local playback
-            return;
+        if (enableOutputSwitcherForSystemRouting()) {
+            mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager);
+        } else {
+            if (mMediaController == null) {
+                // No active local playback
+                return;
+            }
         }
 
+
         if (Utils.isAudioModeOngoingCall(mContext)) {
             // Ongoing call status, switch entry for media will be disabled.
             mPreference.setVisible(false);
@@ -95,9 +104,14 @@
                 || (connectedLeAudioDevices != null && !connectedLeAudioDevices.isEmpty()))) {
             activeDevice = findActiveDevice();
         }
-        mPreference.setTitle(mContext.getString(R.string.media_output_label_title,
-                com.android.settings.Utils.getApplicationLabel(mContext,
-                        mMediaController.getPackageName())));
+
+        if (mMediaController == null) {
+            mPreference.setTitle(mContext.getString(R.string.media_output_title_without_playing));
+        } else {
+            mPreference.setTitle(mContext.getString(R.string.media_output_label_title,
+                    com.android.settings.Utils.getApplicationLabel(mContext,
+                    mMediaController.getPackageName())));
+        }
         mPreference.setSummary((activeDevice == null) ?
                 mContext.getText(R.string.media_output_default_summary) :
                 activeDevice.getAlias());
@@ -145,13 +159,19 @@
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
         if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
-            mContext.sendBroadcast(new Intent()
-                    .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
-                    .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
-                    .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
-                            mMediaController.getPackageName())
-                    .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
-                            mMediaController.getSessionToken()));
+            if (enableOutputSwitcherForSystemRouting() && mMediaController == null) {
+                mContext.sendBroadcast(new Intent()
+                        .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG)
+                        .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME));
+            } else if (mMediaController != null) {
+                mContext.sendBroadcast(new Intent()
+                        .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
+                        .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
+                        .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+                                mMediaController.getPackageName())
+                        .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
+                                mMediaController.getSessionToken()));
+            }
             return true;
         }
         return false;
diff --git a/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt b/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt
index 7615442..345d931 100644
--- a/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt
@@ -23,7 +23,9 @@
 import androidx.compose.material.icons.outlined.Report
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settings.R
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -35,6 +37,9 @@
 import com.android.settingslib.spaprivileged.model.app.hasFlag
 import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
 import com.android.settingslib.spaprivileged.model.app.userId
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
 class AppForceStopButton(
     private val packageInfoPresenter: PackageInfoPresenter,
@@ -47,9 +52,13 @@
     fun getActionButton(app: ApplicationInfo): ActionButton {
         val dialogPresenter = confirmDialogPresenter()
         return ActionButton(
-            text = context.getString(R.string.force_stop),
+            text = stringResource(R.string.force_stop),
             imageVector = Icons.Outlined.Report,
-            enabled = isForceStopButtonEnable(app),
+            enabled = remember(app) {
+                flow {
+                    emit(isForceStopButtonEnable(app))
+                }.flowOn(Dispatchers.Default)
+            }.collectAsStateWithLifecycle(false).value,
         ) { onForceStopButtonClicked(app, dialogPresenter) }
     }
 
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 3b7f579..6882963 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -32,10 +32,10 @@
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settings.flags.Flags
 import com.android.settings.R
 import com.android.settings.applications.AppInfoBase
 import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.flags.Flags
 import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
 import com.android.settings.spa.app.appcompat.UserAspectRatioAppPreference
 import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
@@ -45,7 +45,6 @@
 import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
 import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.compose.LifecycleEffect
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Category
@@ -75,7 +74,7 @@
             PackageInfoPresenter(context, packageName, userId, coroutineScope)
         }
         AppInfoSettings(packageInfoPresenter)
-        packageInfoPresenter.PackageRemoveDetector()
+        packageInfoPresenter.PackageFullyRemovedEffect()
     }
 
     @Composable
@@ -120,8 +119,7 @@
 
 @Composable
 private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
-    LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
-    val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
+    val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?:return
     val app = checkNotNull(packageInfo.applicationInfo)
     val featureFlags: FeatureFlags = FeatureFlagsImpl()
     RegularScaffold(
@@ -131,7 +129,7 @@
             AppInfoSettingsMoreOptions(packageInfoPresenter, app)
         }
     ) {
-        val appInfoProvider = remember { AppInfoProvider(packageInfo) }
+        val appInfoProvider = remember(packageInfo) { AppInfoProvider(packageInfo) }
 
         appInfoProvider.AppInfo()
 
diff --git a/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
index 760d375..c8e8d35 100644
--- a/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
@@ -28,7 +28,6 @@
 import androidx.navigation.navArgument
 import com.android.settings.R
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.compose.LifecycleEffect
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spaprivileged.model.app.toRoute
 import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
@@ -54,7 +53,7 @@
             PackageInfoPresenter(context, packageName, userId, coroutineScope)
         }
         CloneAppInfoSettings(packageInfoPresenter)
-        packageInfoPresenter.PackageRemoveDetector()
+        packageInfoPresenter.PackageFullyRemovedEffect()
     }
 
     @Composable
@@ -70,7 +69,6 @@
 
 @Composable
 private fun CloneAppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
-    LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
     val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
     RegularScaffold(
             title = stringResource(R.string.application_info_label),
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 6eee72e..8d0f0bb 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -32,14 +32,20 @@
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spaprivileged.framework.common.activityManager
 import com.android.settingslib.spaprivileged.framework.common.asUser
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow
 import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
 
 private const val TAG = "PackageInfoPresenter"
 
@@ -58,34 +64,33 @@
     private val userHandle = UserHandle.of(userId)
     val userContext by lazy { context.asUser(userHandle) }
     val userPackageManager: PackageManager by lazy { userContext.packageManager }
-    private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
 
-    val flow: StateFlow<PackageInfo?> = _flow
-
-    fun reloadPackageInfo() {
-        coroutineScope.launch(Dispatchers.IO) {
-            _flow.value = getPackageInfo()
-        }
-    }
+    val flow: StateFlow<PackageInfo?> = merge(
+        flowOf(null), // kick an initial value
+        context.broadcastReceiverAsUserFlow(
+            intentFilter = IntentFilter().apply {
+                addAction(Intent.ACTION_PACKAGE_CHANGED)
+                addAction(Intent.ACTION_PACKAGE_REPLACED)
+                addAction(Intent.ACTION_PACKAGE_RESTARTED)
+                addDataScheme("package")
+            },
+            userHandle = userHandle,
+        ),
+    ).map { getPackageInfo() }
+        .stateIn(coroutineScope + Dispatchers.Default, SharingStarted.Eagerly, null)
 
     /**
-     * Detects the package removed event.
+     * Detects the package fully removed event, and close the current page.
      */
     @Composable
-    fun PackageRemoveDetector() {
-        val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED).apply {
+    fun PackageFullyRemovedEffect() {
+        val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_FULLY_REMOVED).apply {
             addDataScheme("package")
         }
         val navController = LocalNavController.current
         DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
             if (packageName == intent.data?.schemeSpecificPart) {
-                val packageInfo = flow.value
-                if (packageInfo != null && packageInfo.applicationInfo?.isSystemApp == true) {
-                    // System app still exists after uninstalling the updates, refresh the page.
-                    reloadPackageInfo()
-                } else {
-                    navController.navigateBack()
-                }
+                navController.navigateBack()
             }
         }
     }
@@ -97,7 +102,6 @@
             userPackageManager.setApplicationEnabledSetting(
                 packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
             )
-            reloadPackageInfo()
         }
     }
 
@@ -108,7 +112,6 @@
             userPackageManager.setApplicationEnabledSetting(
                 packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
             )
-            reloadPackageInfo()
         }
     }
 
@@ -123,7 +126,6 @@
         logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
         coroutineScope.launch(Dispatchers.IO) {
             userPackageManager.deletePackageAsUser(packageName, null, 0, userId)
-            reloadPackageInfo()
         }
     }
 
@@ -133,7 +135,6 @@
         coroutineScope.launch(Dispatchers.Default) {
             Log.d(TAG, "Stopping package $packageName")
             context.activityManager.forceStopPackageAsUser(packageName, userId)
-            reloadPackageInfo()
         }
     }
 
@@ -141,7 +142,7 @@
         metricsFeatureProvider.action(context, category, packageName)
     }
 
-    private fun getPackageInfo() =
+    private fun getPackageInfo(): PackageInfo? =
         packageManagers.getPackageInfoAsUser(
             packageName = packageName,
             flags = PackageManager.MATCH_ANY_USER.toLong() or
diff --git a/src/com/android/settings/system/DeveloperOptionsController.kt b/src/com/android/settings/system/DeveloperOptionsController.kt
index fe7fb4d..6874ec7 100644
--- a/src/com/android/settings/system/DeveloperOptionsController.kt
+++ b/src/com/android/settings/system/DeveloperOptionsController.kt
@@ -34,6 +34,7 @@
 import com.android.settings.spa.preference.ComposePreferenceController
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.ui.SettingsIcon
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
 import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
 import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
@@ -41,7 +42,9 @@
 class DeveloperOptionsController(context: Context, preferenceKey: String) :
     ComposePreferenceController(context, preferenceKey) {
 
-    override fun getAvailabilityStatus() = AVAILABLE
+    override fun getAvailabilityStatus() =
+        if (mContext.userManager.isAdminUser) AVAILABLE
+        else DISABLED_FOR_USER
 
     private val isDevelopmentSettingsEnabledFlow = context.settingsGlobalBooleanFlow(
         name = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
diff --git a/src/com/android/settings/widget/SettingsMainSwitchPreference.java b/src/com/android/settings/widget/SettingsMainSwitchPreference.java
index fc43e58..9f6d787 100644
--- a/src/com/android/settings/widget/SettingsMainSwitchPreference.java
+++ b/src/com/android/settings/widget/SettingsMainSwitchPreference.java
@@ -78,7 +78,7 @@
         holder.setDividerAllowedAbove(false);
         holder.setDividerAllowedBelow(false);
 
-        if (mRestrictedHelper != null) {
+        if (mEnforcedAdmin == null && mRestrictedHelper != null) {
             mEnforcedAdmin = mRestrictedHelper.checkRestrictionEnforced();
         }
         mMainSwitchBar = (SettingsMainSwitchBar) holder.findViewById(R.id.main_switch_bar);
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index fa55f35..6d3c748 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -50,6 +50,8 @@
         "src/**/*.kt",
     ],
 
+    test_suites: ["general-tests"],
+
     static_libs: [
         "Robolectric_shadows_androidx_fragment_upstream",
         "Settings_robolectric_meta_service_file",
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
index a91f627..652bd03 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
@@ -18,6 +18,7 @@
 package com.android.settings.media;
 
 import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,6 +39,7 @@
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Process;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 
 import androidx.slice.Slice;
@@ -55,6 +57,7 @@
 
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -96,6 +99,9 @@
     @Mock
     private Drawable mTestDrawable;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private Context mContext;
     private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice;
     private AudioManager mAudioManager;
@@ -255,6 +261,34 @@
     }
 
     @Test
+    public void onNotifyChange_withoutMediaControllerFlagEnabled_verifyIntentExtra() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        doReturn(null).when(sMediaOutputIndicatorWorker)
+                .getActiveLocalMediaController();
+        ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
+
+        mMediaOutputIndicatorSlice.onNotifyChange(null);
+        verify(mContext, times(2)).sendBroadcast(argument.capture());
+        List<Intent> intentList = argument.getAllValues();
+        Intent intent = intentList.get(0);
+
+        assertThat(intent.getAction()).isEqualTo(
+                MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
+        assertThat(TextUtils.equals(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME,
+                intent.getPackage())).isTrue();
+    }
+
+    @Test
+    public void onNotifyChange_withoutMediaControllerFlagDisabled_doNothing() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        doReturn(null).when(sMediaOutputIndicatorWorker)
+                .getActiveLocalMediaController();
+
+        mMediaOutputIndicatorSlice.onNotifyChange(null);
+    }
+
+
+    @Test
     public void isVisible_allConditionMatched_returnTrue() {
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         mDevices.add(mDevice1);
@@ -268,6 +302,7 @@
 
     @Test
     public void isVisible_noActiveSession_returnFalse() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
         mAudioManager.setMode(AudioManager.MODE_NORMAL);
         mDevices.add(mDevice1);
 
@@ -278,6 +313,19 @@
         assertThat(mMediaOutputIndicatorSlice.isVisible()).isFalse();
     }
 
+    @Test
+    public void isVisible_noActiveSession_returnTrue() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        mDevices.add(mDevice1);
+
+        when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
+        doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+                .getActiveLocalMediaController();
+
+        assertThat(mMediaOutputIndicatorSlice.isVisible()).isTrue();
+    }
+
     private void initPackage() {
         mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
         mAppInfo = new ApplicationInfo();
diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java
index 63b6c3e..50e9a5c 100644
--- a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java
@@ -13,16 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.settings.security;
 
 import static com.android.settings.security.ContentProtectionTogglePreferenceController.KEY_CONTENT_PROTECTION_PREFERENCE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
@@ -32,6 +34,7 @@
 
 import com.android.settings.testutils.shadow.ShadowUtils;
 import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.RestrictedLockUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,8 +58,9 @@
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    @Mock private PreferenceScreen mScreen;
+    @Mock private PreferenceScreen mMockScreen;
 
+    private RestrictedLockUtils.EnforcedAdmin mAdmin;
     private SettingsMainSwitchPreference mSwitchPreference;
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private ContentProtectionTogglePreferenceController mController;
@@ -65,9 +69,10 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new ContentProtectionTogglePreferenceController(mContext, "key");
+        mController = new TestContentProtectionTogglePreferenceController();
         mSwitchPreference = new SettingsMainSwitchPreference(mContext);
-        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mSwitchPreference);
+        when(mMockScreen.findPreference(mController.getPreferenceKey()))
+                .thenReturn(mSwitchPreference);
         mSettingBackupValue = getContentProtectionGlobalSetting();
         Settings.Global.putInt(mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 0);
     }
@@ -78,6 +83,7 @@
                 mContext.getContentResolver(),
                 KEY_CONTENT_PROTECTION_PREFERENCE,
                 mSettingBackupValue);
+        ShadowUtils.reset();
     }
 
     @Test
@@ -86,6 +92,49 @@
     }
 
     @Test
+    public void displayPreference() {
+        setUpFullyManagedMode();
+        SettingsMainSwitchPreference mockSwitchPreference =
+                mock(SettingsMainSwitchPreference.class);
+        when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+        when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
+
+        mController = new TestContentProtectionTogglePreferenceController();
+        mController.displayPreference(mMockScreen);
+
+        assertThat(mockSwitchPreference).isNotNull();
+    }
+
+    @Test
+    public void updateState_notFullyManagedMode_enabled() {
+        SettingsMainSwitchPreference mockSwitchPreference =
+                mock(SettingsMainSwitchPreference.class);
+        when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+        when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
+
+        mController = new TestContentProtectionTogglePreferenceController();
+        mController.displayPreference(mMockScreen);
+        mController.updateState(mockSwitchPreference);
+
+        verify(mockSwitchPreference, never()).setDisabledByAdmin(any());
+    }
+
+    @Test
+    public void updateState_fullyManagedMode_disabled() {
+        setUpFullyManagedMode();
+        SettingsMainSwitchPreference mockSwitchPreference =
+                mock(SettingsMainSwitchPreference.class);
+        when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+        when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
+
+        mController = new TestContentProtectionTogglePreferenceController();
+        mController.displayPreference(mMockScreen);
+        mController.updateState(mockSwitchPreference);
+
+        verify(mockSwitchPreference).setDisabledByAdmin(mAdmin);
+    }
+
+    @Test
     public void isChecked_settingTurnOn() {
         Settings.Global.putInt(mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 1);
 
@@ -94,15 +143,18 @@
 
     @Test
     public void isChecked_fullyManagedMode_settingTurnOff() {
-        final ComponentName componentName =
-                ComponentName.unflattenFromString("com.android.test/.DeviceAdminReceiver");
-        ShadowUtils.setDeviceOwnerComponent(componentName);
+        setUpFullyManagedMode();
         Settings.Global.putInt(mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 1);
+        SettingsMainSwitchPreference mockSwitchPreference =
+                mock(SettingsMainSwitchPreference.class);
+        when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+        when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
 
-        ContentProtectionTogglePreferenceController controller =
-                new ContentProtectionTogglePreferenceController(mContext, "key");
+        mController = new TestContentProtectionTogglePreferenceController();
+        mController.displayPreference(mMockScreen);
+        mController.updateState(mockSwitchPreference);
 
-        assertThat(controller.isChecked()).isFalse();
+        assertThat(mController.isChecked()).isFalse();
     }
 
     @Test
@@ -122,7 +174,6 @@
 
     @Test
     public void onSwitchChanged_switchChecked_manuallyEnabled() {
-        mController.displayPreference(mScreen);
         mController.setChecked(false);
 
         mController.onCheckedChanged(/* switchView= */ null, /* isChecked= */ true);
@@ -132,8 +183,6 @@
 
     @Test
     public void onSwitchChanged_switchUnchecked_manuallyDisabled() {
-        mController.displayPreference(mScreen);
-
         mController.onCheckedChanged(/* switchView= */ null, /* isChecked= */ false);
 
         assertThat(getContentProtectionGlobalSetting()).isEqualTo(-1);
@@ -143,4 +192,21 @@
         return Settings.Global.getInt(
                 mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 0);
     }
+
+    private void setUpFullyManagedMode() {
+        mAdmin = new RestrictedLockUtils.EnforcedAdmin();
+    }
+
+    private class TestContentProtectionTogglePreferenceController
+            extends ContentProtectionTogglePreferenceController {
+
+        TestContentProtectionTogglePreferenceController() {
+            super(ContentProtectionTogglePreferenceControllerTest.this.mContext, "key");
+        }
+
+        @Override
+        protected RestrictedLockUtils.EnforcedAdmin getEnforcedAdmin() {
+            return mAdmin;
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
index 151d1f2..a272d9c 100644
--- a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
@@ -21,10 +21,12 @@
 
 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
 import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
 
 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.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -38,6 +40,8 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.FeatureFlagUtils;
 
 import androidx.preference.ListPreference;
@@ -61,6 +65,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -113,6 +118,9 @@
     @Mock
     private CachedBluetoothDevice mCachedBluetoothDeviceR;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private Context mContext;
     private PreferenceScreen mScreen;
     private ListPreference mPreference;
@@ -238,6 +246,7 @@
 
     @Test
     public void onStart_shouldRegisterCallbackAndRegisterReceiver() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
         mController.onStart();
 
         verify(mLocalBluetoothManager.getEventManager()).registerCallback(
@@ -248,6 +257,7 @@
 
     @Test
     public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
         mController.onStart();
         mController.onStop();
 
@@ -257,6 +267,45 @@
         verify(mLocalBluetoothManager).setForegroundActivity(null);
     }
 
+    @Test
+    public void onStart_shouldRegisterCallbackAndRegisterReceiverWithDefaultMediaOutput() {
+        MediaSessionManager mediaSessionManager =
+                spy(mContext.getSystemService(MediaSessionManager.class));
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager);
+        mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
+
+        mController.onStart();
+
+        verify(mLocalBluetoothManager.getEventManager()).registerCallback(
+                any(BluetoothCallback.class));
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+        verify(mLocalBluetoothManager).setForegroundActivity(mContext);
+        verify(mediaSessionManager).addOnActiveSessionsChangedListener(
+                any(MediaSessionManager.OnActiveSessionsChangedListener.class), any(), any());
+    }
+
+
+    @Test
+    public void onStop_shouldUnregisterCallbackAndUnregisterReceiverWithDefaultMediaOutput() {
+        MediaSessionManager mediaSessionManager =
+                spy(mContext.getSystemService(MediaSessionManager.class));
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager);
+        mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
+        mController.onStart();
+
+        mController.onStop();
+
+        verify(mLocalBluetoothManager.getEventManager()).unregisterCallback(
+                any(BluetoothCallback.class));
+        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+        verify(mLocalBluetoothManager).setForegroundActivity(null);
+        verify(mediaSessionManager).removeOnActiveSessionsChangedListener(
+                any(MediaSessionManager.OnActiveSessionsChangedListener.class));
+    }
+
     /**
      * Audio stream output to bluetooth sco headset which is the subset of all sco device.
      * isStreamFromOutputDevice should return true.
diff --git a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java
index 5a92a08..b9f9b16 100644
--- a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java
@@ -21,6 +21,8 @@
 import static android.media.AudioSystem.DEVICE_OUT_EARPIECE;
 import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
 
+import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -45,6 +47,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
@@ -66,6 +69,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -103,6 +107,9 @@
     private static final String TEST_PACKAGE_NAME = "com.test.packagename";
     private static final String TEST_APPLICATION_LABEL = "APP Test Label";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private LocalBluetoothManager mLocalManager;
     @Mock
@@ -227,6 +234,8 @@
         mScreen.addPreference(mPreference);
         mController.displayPreference(mScreen);
         mController.setCallback(mAudioSwitchPreferenceCallback);
+
+        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
     }
 
     @After
@@ -314,6 +323,7 @@
 
     @Test
     public void updateState_noActiveLocalPlayback_noTitle() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
         mPlaybackState = new PlaybackState.Builder()
                 .setState(PlaybackState.STATE_NONE, 0, 1)
                 .build();
@@ -326,6 +336,48 @@
     }
 
     @Test
+    public void updateState_noActiveLocalPlayback_checkTitle() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        mPlaybackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_NONE, 0, 1)
+                .build();
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+        mController.displayPreference(mScreen);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getTitle().toString()).isEqualTo(
+                mContext.getString(R.string.media_output_title_without_playing,
+                        TEST_APPLICATION_LABEL));
+    }
+
+    @Test
+    public void updateState_withNullMediaController_noTitle() {
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        mMediaControllers.clear();
+        mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getTitle()).isNull();
+    }
+
+    @Test
+    public void updateState_withNullMediaController_checkTitle() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        mMediaControllers.clear();
+        mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+        mController.displayPreference(mScreen);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getTitle().toString()).isEqualTo(
+                mContext.getString(R.string.media_output_title_without_playing,
+                        TEST_APPLICATION_LABEL));
+    }
+
+    @Test
     public void updateState_withActiveLocalPlayback_checkTitle() {
         initPackage();
         mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
@@ -349,6 +401,39 @@
                 .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
     }
 
+    @Test
+    public void handlePreferenceTreeClick_WithNoLocalPlaybackFlagEnabled_verifyIntentExtra() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        mPlaybackState = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_NONE, 0, 1)
+                .build();
+        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+        mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+        mPreference.setKey(TEST_KEY);
+
+        mController.handlePreferenceTreeClick(mPreference);
+
+        verify(mContext).sendBroadcast(intentCaptor.capture());
+        assertThat(intentCaptor.getValue().getAction())
+                .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
+    }
+
+    @Test
+    public void handlePreferenceTreeClick_WithNullControllerFlagEnabled_verifyIntentExtra() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        mMediaControllers.clear();
+        mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+        mPreference.setKey(TEST_KEY);
+
+        mController.handlePreferenceTreeClick(mPreference);
+
+        verify(mContext).sendBroadcast(intentCaptor.capture());
+        assertThat(intentCaptor.getValue().getAction())
+                .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
+    }
+
     /**
      * Default status
      * Preference should be invisible
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt
index 6c5cb85..ecb540c 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt
@@ -20,8 +20,6 @@
 import android.app.settings.SettingsEnums
 import android.content.Context
 import android.content.Intent
-import android.content.pm.FakeFeatureFlagsImpl
-import android.content.pm.Flags
 import android.content.pm.PackageManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,91 +28,79 @@
 import com.android.settingslib.spaprivileged.framework.common.activityManager
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.verify
-import org.mockito.Spy
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class PackageInfoPresenterTest {
-    @get:Rule
-    val mockito: MockitoRule = MockitoJUnit.rule()
 
-    @Spy
-    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val mockPackageManager = mock<PackageManager>()
 
-    @Mock
-    private lateinit var packageManager: PackageManager
+    private val mockActivityManager = mock<ActivityManager>()
 
-    @Mock
-    private lateinit var activityManager: ActivityManager
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { packageManager } doReturn mockPackageManager
+        on { activityManager } doReturn mockActivityManager
+        doNothing().whenever(mock).startActivityAsUser(any(), any())
+        mock.mockAsUser()
+    }
 
-    @Mock
-    private lateinit var packageManagers: IPackageManagers
+    private val packageManagers = mock<IPackageManagers>()
 
     private val fakeFeatureFactory = FakeFeatureFactory()
     private val metricsFeatureProvider = fakeFeatureFactory.metricsFeatureProvider
 
-    @Before
-    fun setUp() {
-        context.mockAsUser()
-        whenever(context.packageManager).thenReturn(packageManager)
-        whenever(context.activityManager).thenReturn(activityManager)
-    }
-
     @Test
-    fun enable() = runTest {
-        coroutineScope {
-            val packageInfoPresenter =
-                PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+    fun enable() = runBlocking {
+        val packageInfoPresenter =
+            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
 
-            packageInfoPresenter.enable()
-        }
+        packageInfoPresenter.enable()
+        delay(100)
 
         verifyAction(SettingsEnums.ACTION_SETTINGS_ENABLE_APP)
-        verify(packageManager).setApplicationEnabledSetting(
+        verify(mockPackageManager).setApplicationEnabledSetting(
             PACKAGE_NAME, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
         )
     }
 
     @Test
-    fun disable() = runTest {
-        coroutineScope {
-            val packageInfoPresenter =
-                PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+    fun disable() = runBlocking {
+        val packageInfoPresenter =
+            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
 
-            packageInfoPresenter.disable()
-        }
+        packageInfoPresenter.disable()
+        delay(100)
 
         verifyAction(SettingsEnums.ACTION_SETTINGS_DISABLE_APP)
-        verify(packageManager).setApplicationEnabledSetting(
+        verify(mockPackageManager).setApplicationEnabledSetting(
             PACKAGE_NAME, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
         )
     }
 
     @Test
-    fun startUninstallActivity() = runTest {
-        doNothing().`when`(context).startActivityAsUser(any(), any())
+    fun startUninstallActivity() = runBlocking {
         val packageInfoPresenter =
-            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
 
         packageInfoPresenter.startUninstallActivity()
 
         verifyAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
-        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
-        verify(context).startActivityAsUser(intentCaptor.capture(), any())
-        with(intentCaptor.value) {
+        val intent = argumentCaptor<Intent> {
+            verify(context).startActivityAsUser(capture(), any())
+        }.firstValue
+        with(intent) {
             assertThat(action).isEqualTo(Intent.ACTION_UNINSTALL_PACKAGE)
             assertThat(data?.schemeSpecificPart).isEqualTo(PACKAGE_NAME)
             assertThat(getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, true)).isEqualTo(false)
@@ -122,76 +108,39 @@
     }
 
     @Test
-    fun clearInstantApp() = runTest {
-        coroutineScope {
-            val packageInfoPresenter =
-                PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+    fun clearInstantApp() = runBlocking {
+        val packageInfoPresenter =
+            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
 
-            packageInfoPresenter.clearInstantApp()
-        }
+        packageInfoPresenter.clearInstantApp()
+        delay(100)
 
         verifyAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
-        verify(packageManager).deletePackageAsUser(PACKAGE_NAME, null, 0, USER_ID)
+        verify(mockPackageManager).deletePackageAsUser(PACKAGE_NAME, null, 0, USER_ID)
     }
 
     @Test
-    fun forceStop() = runTest {
-        coroutineScope {
-            val packageInfoPresenter =
-                PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+    fun forceStop() = runBlocking {
+        val packageInfoPresenter =
+            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
 
-            packageInfoPresenter.forceStop()
-        }
+        packageInfoPresenter.forceStop()
+        delay(100)
 
         verifyAction(SettingsEnums.ACTION_APP_FORCE_STOP)
-        verify(activityManager).forceStopPackageAsUser(PACKAGE_NAME, USER_ID)
+        verify(mockActivityManager).forceStopPackageAsUser(PACKAGE_NAME, USER_ID)
     }
 
     @Test
-    fun logAction() = runTest {
+    fun logAction() = runBlocking {
         val packageInfoPresenter =
-            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+            PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
 
         packageInfoPresenter.logAction(123)
 
         verifyAction(123)
     }
 
-    @Test
-    fun reloadPackageInfo_archivingDisabled() = runTest {
-        coroutineScope {
-            val fakeFeatureFlags = FakeFeatureFlagsImpl()
-            fakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, false)
-            val packageInfoPresenter =
-                PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers, fakeFeatureFlags)
-
-            packageInfoPresenter.reloadPackageInfo()
-        }
-
-        val flags = PackageManager.MATCH_ANY_USER.toLong() or
-            PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
-            PackageManager.GET_PERMISSIONS.toLong()
-        verify(packageManagers).getPackageInfoAsUser(PACKAGE_NAME, flags, USER_ID)
-    }
-
-    @Test
-    fun reloadPackageInfo_archivingEnabled() = runTest {
-        coroutineScope {
-            val fakeFeatureFlags = FakeFeatureFlagsImpl()
-            fakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
-            val packageInfoPresenter =
-                PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers, fakeFeatureFlags)
-
-            packageInfoPresenter.reloadPackageInfo()
-        }
-
-        val flags = PackageManager.MATCH_ANY_USER.toLong() or
-            PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
-            PackageManager.GET_PERMISSIONS.toLong() or
-            PackageManager.MATCH_ARCHIVED_PACKAGES
-        verify(packageManagers).getPackageInfoAsUser(PACKAGE_NAME, flags, USER_ID)
-    }
-
     private fun verifyAction(category: Int) {
         verify(metricsFeatureProvider).action(context, category, PACKAGE_NAME)
     }
diff --git a/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt b/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt
index 8707065..ad2fbae 100644
--- a/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.Intent
+import android.os.UserManager
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
@@ -25,7 +26,9 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.SettingsActivity
+import com.android.settings.core.BasePreferenceController
 import com.android.settings.development.DevelopmentSettingsDashboardFragment
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -33,7 +36,10 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
@@ -42,13 +48,38 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    private val mockUserManager = mock<UserManager>()
+
     private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { userManager } doReturn mockUserManager
         doNothing().whenever(mock).startActivity(any())
     }
 
     private val controller = DeveloperOptionsController(context, TEST_KEY)
 
     @Test
+    fun getAvailabilityStatus_isAdminUser_returnAvailable() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn true
+        }
+
+        val availabilityStatus = controller.getAvailabilityStatus()
+
+        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
+    }
+
+    @Test
+    fun getAvailabilityStatus_notAdminUser_returnDisabledForUser() {
+        mockUserManager.stub {
+            on { isAdminUser } doReturn false
+        }
+
+        val availabilityStatus = controller.getAvailabilityStatus()
+
+        assertThat(availabilityStatus).isEqualTo(BasePreferenceController.DISABLED_FOR_USER)
+    }
+
+    @Test
     fun title_isDisplayed() {
         composeTestRule.setContent {
             controller.DeveloperOptionsPreference()